线程的基本概念
线程的简单理解:线程是一个程序里面不同的执行路径
进程是一个静态的概念,main方法是进程的主线程
线程和进程的区别
1)每一个进程都有独立的代码和数据空间(进程上下文)。进程间的切换会有较大的开销
2)线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立
的运行栈和程序计数器(PC),线程切换的开销小
3)多进程:在操作系统中能同时运行多个任务(程序)
4)多线程:在同一应用程序中有多个顺序流同时执行
Java的线程是通过java.lang.Thread类来实现的,每一个Thread对象代表一个新的线程
启动线程需要有两步:第一步创建一个自己的线程对象,然后new一个Thread对象,然后
让它启动
Java虚拟机启动时会有一个由主方法(public static void main() {})所定义的线程,主线程
可以通过创建Thread的实例来创建新的线程,只要new一个Thread对象,一个新的线程就
出现了
每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()
称为线程体,在run()方法里面写什么语句,它就执行什么语句
通过调用Thead类的start()方法来启动一个线程
线程的创建和启动
有两种方法创建新的线程
第一种,定义线程类实现Runnable接口
这个类的对象可以当作一个线程执行?
当定义的类实现了Runnable接口,它就是一个线程类,用Thread对象来启动线程
1)Thread myThread = new Thread(target) //target为Runnable接口类型
2)Runnable中只有一个方法:
public void run(); //用以定义线程运行体
3)使用Runnable接口可以为多个线程提供共享的数据
4)在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法:
public static Thread currentThread(); //获取当前线程的引用
第二种,继承Thread类并重写其run()方法
class MyThread extends Thread {
public void run() {...}
}
然后生成该类的对象:
MyThread myThread = new MyThread(...)
线程启动必须调用Thread类的start()方法
推荐只要能实现Runnable接口就不要从Thread类继承,实现接口更灵活,还能继承其它类和接口
/*
* TestThread1.java
*/
public class TestThread1 {
public static void main(String args[]) {
Runner1 r = new Runner1();
Thread t = new Thread(r);
t.start(); //启动一个新的线程
for (int i=0; i<100; i++) {
System.out.println("Main Thread:-----" + i);
}
}
}
class Runner1 implements Runnable {
public void run() {
for (int i=0; i<100; i++) {
System.out.println("Runner1 :" + i);
}
}
}线程的状态
线程控制基本方法
//判断线程是否还"活"着,即线程是否还未终止
isAlive()
//获得线程的优先级数值
getPriority()
//设置线程的优先级数值,优先级越高的线程获得CPU的时间越多
setPriority()
//将当前线程睡眠指定毫秒数
Thread.sleep()
sleep方法是Thread的静态方法,会抛InterruptedException异常
在哪个线程里调用sleep方法,哪个线程就睡眠
//调用某线程的该方法,将当前线程与该线程"合并",即等待该线程结束,再
恢复当前线程的运行
join()
//让出CPU,当前线程进入就绪队列等待调度
yield()
//当前线程进入对象的wait pool
wait()
//唤醒对象的wait pool中的一个/所有等待线程
notify() / notifyAll()
sleep / join / yield 方法
sleep方法
可以调用Thread的静态方法:
public static void sleep(long millis) throws InterruptedException
使得当前线程休眠(暂时停止执行millis毫秒)
由于是静态方法,sleep可以由类名直接调用:
Thread.sleep(...)
当sleep执行时,该线程被中断会抛出InterruptedException异常
/*
* TestInterrupt.java
*/
import java.util.*;
public class TestInterrupt {
public static void main(String args[]) {
MyThread thread = new MyThread();
thread.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
thread.interrupt();
}
}
class MyThread extends Thread {
public void run() {
while (true) {
System.out.println("===" + new Date() + "===");
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
} join方法合并某个线程
/*
* TestJoin.java
*/
public class TestJoin {
public static void main(String args[]) {
MyThread2 t1 = new MyThread2("abc");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {}
for(int i=0; i<=10; i++) {
System.out.println("i am main thread");
}
}
}
class MyThread2 extends Thread {
MyThread2(String s) {
super(s);
}
public void run() {
for(int i=0; i<=10; i++) {
System.out.println("i am " + getName());
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
} yield方法让出CPU,给其他线程执行的机会
/*
* TestYield.java
*/
public class TestYield {
public static void main(String args[]) {
MyThread3 t1 = new MyThread3("t1");
MyThread3 t2 = new MyThread3("t2");
t1.start();
t2.start();
}
}
class MyThread3 extends Thread {
MyThread3(String s) {
super(s);
}
public void run() {
for(int i=1; i<=100; i++) {
System.out.println(getName() + ": " + i);
if (i%10==0)
yield();
}
}
} 如何让线程在死循环退出?1)stop() //太粗暴,不推荐
2)在线程内部使用sleep,捕捉InterruptedException异常,然后退出
3)如下
boolean flag = true;
public void run() {
while(flag) {
...
}
}
只要调用thread.flag = false,run方法就结束了,线程退出
线程的调度和优先级
1)Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程
线程调度器按照线程的优先级决定应调度哪个线程来执行
2)线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
3)使用下述方法获得或设置线程对象的优先级
int getPriority();
void setPriority(int newPriority);
Thread类定义了静态常量,表示优先级MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY
run方法一结束,线程就结束
线程的状态控制
Thread.currentThread().isAlive(); //拿到当前线程,判断是否活着
线程同步
对访问同一份资源的多个线程之间来进行协调,叫做线程同步
解决办法之一:
加锁(独占)
两个线程不同步,问题出在一个线程在执行过程中,被另一个线程打断了
(本来应该是原子性的,但是现在中间被打断了)
在Java语言中,引入了对象互斥锁的概念,保证共享数据操作的完整性。
每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,
只能有一个线程访问该对象
synchronized关键字
关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized
修饰时,表明该对象在任一时刻只能由一个线程访问
这个关键字会锁定某一段代码,它的内部含义是当执行这段代码的过程之中,
锁定当前对象。如果另一个人也想访问这个对象,只能等着锁释放
1)锁定某一个东西
synchronized (this) { //锁定当前对象,互斥锁
...
}
大括号里,在一个线程执行语句过程当中,不会被另外一个线程打断
2)更简便写法,在执行这个方法的过程中,锁定当前对象
public synchronized void XXX() { //在执行方法的过程中,锁定当前对象
...
}
注意:这里是锁定当前对象,即这个方法的所在对象,同一个对象里面其它加锁的
方法就不能执行了
/*
* TestSync.java
*/
public class TestSync implements Runnable {
Timer timer = new Timer();
public static void main(String args[]) {
TestSync test = new TestSync();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
private static int num = 0;
//注意区分加synchronized和不加synchronized的区别
public /*synchronized*/ void add(String name) {
//synchronized (this) {
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println(name+", 你是第"+num+"个使用timer的线程");
//}
}
} 死锁
原理:都需要等待对方释放手中的锁
一个解决死锁:把锁的粒度加粗,锁定当前整个对象
/*
* TestDeadLock.java
*/
public class TestDeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public void run() {
System.out.println("flag" + flag);
if (flag == 1) {
synchronized(o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized(o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o1) {
System.out.println("0");
}
}
}
}
public static void main(String args[]) {
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
} 一个类里加锁的方法,不影响其他线程访问其他方法一个资源能不能够正确的访问一致,必须把访问这个资源的所有方法都要考虑到
只给一个方法加锁是不行的,必须把能访问这个资源的所有方法都加锁
互斥:在某一个时间段,保证只有一个线程进入到这个方法体里面
生产者消费者问题
wait() //当前线程阻塞,有人调用notify方法后就醒了,然后抢锁,谁抢到谁运行
wait的时候不再拥有锁
this.wait() 指当前正在访问这个对象方法的线程wait(线程先拿到锁,访问对象的方法)
notify() //叫醒一个现在wait在我这个对象上的线程
notifyAll() //叫醒在这个对象上wait的所有线程
wait sleep区别
wait时,别的线程可以访问锁定对象,锁不归我所有了
调用wait方法的时候必须锁定该对象
sleep时,睡着了也抱着那把锁,别的线程也不可以访问锁定对象
/*
* ProducerConsumer.java
*/
public class ProducerConsumer {
public static void main(String args[]) {
SyncStack s = new SyncStack();
Producer p = new Producer(s);
Consumer c = new Consumer(s);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
//包子
class WoTou {
int id;
WoTou(int id) {
this.id = id;
}
public String toString() {
return "WoTou : " + id;
}
}
//篮子
class SyncStack {
int index = 0;
WoTou[] arrWT = new WoTou[6];
public synchronized void push(WoTou wt) {
while (index == arrWT.length) {
try {
this.wait(); //这里的wait方法是Object类的
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
arrWT[index] = wt;
index++;
}
public synchronized WoTou pop() {
while (index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
return arrWT[index];
}
}
//生产者
class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = new WoTou(i);
ss.push(wt);
System.out.println("生产了: " + wt);
try {
Thread.sleep((int)(Math.random() * 200)); //生产比消费快
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = ss.pop();
System.out.println("消费了: " + wt);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

被折叠的 条评论
为什么被折叠?



