搬水问题,现在有100桶水,找多个人去搬,每个人的工作都是一样的,所以可以使用多线程,基本代码如下:
class Water{
private int num = 100;
private void run(){
// 模拟搬水
while(true){
if(num>0){
System.out.println("搬运的是水"+num);
num--;
}
}
}
}
创建线程有两种方式,一种是将类声明为Thread的子类,另一种是类实现Runnable接口。
为什么有两种方式? 答:对于部分的类而言,它也许已经有了父类,而java是单继承,不允许有两个父类,所以可以采用实现接口的形式。
Thread类:是描述线程本身的,提供操作线程的各种方法。
Runnable接口:它和线程没有直接关系,仅仅提供一个run方法,目的是将需要线程执行的任务书写在run方法中。最终需要将Runnable接口的实现类的对象交给Thread。(换句话说它表示线程要执行的任务接口,实际上Thread类本身就实现了Runnable接口,它其中的run方法也是来自于Runnable接口)
将所需要的的线程类声明为Thread的子类,同时复写run方法
。使用时使用该类的start方法,示例如下:
class Demo extends Thread{
public void run(){
// 业务逻辑
...
}
}
public class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();
d.start();
}
}
相应的,我们改造搬水问题中的类,以实现多线程,代码如下:
注意细节,由于此时是实现了Thread类,所以每个线程都是独立的water对象,大家操作的应该是同100桶水,所以应该改水为static,同理锁也是同一把锁,改为static
class Water extends Thread{
private static int num = 100; // 注意static
private static Object obj = new Object();// 创建锁, // 注意static
public void run(){
// 业务逻辑
while(true){
synchronized(obj){
if(num>0){
System.out.println(Thread.currentThread().getName()+" 搬运的是水 "+num);
num--;
}
}
}
}
}
public class ThreadDemo{
public static void main(String[] args){
Water w = new Water();
Water w2 = new Water();
Water w3 = new Water();
Water w4 = new Water();
w.start();
w2.start();
w3.start();
w4.start();
}
}
运行结果正常。
将线程类实现Runnable接口,同时复写run方法,使用时直接创建该类的对象,并将该对象作为参数传入Thread对象,对Thread对象使用start方法
class Demo implements Runnable{
public void run(){
// 业务逻辑
...
}
}
public class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();
Thread t = new Thread(d);
t.start();
}
}
相应的,我们改造搬水问题中的类,以实现多线程,代码如下
class Water implements Runnable{
private int num = 100;
private Object obj = new Object();// 创建锁
public void run(){
// 业务逻辑
while(true){
synchronized(obj){
if(num>0){
System.out.println(Thread.currentThread().getName()+" 搬运的是水 "+num);
num--;
}
}
}
}
}
public class ThreadDemo{
public static void main(String[] args){
Water w = new Water ();
Thread t = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
Thread t4 = new Thread(w);
t.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果正常。
分为两类:1.synchronized,即同步代码块;2.Lock接口。
private Object lock = new Object();// 创建锁
...
synchronized(lock){//隐式自动上锁
...
}
...
// 创建锁, Lock是一个接口,ReentrantLock是该接口的实现类
private Lock l = new ReentrantLock();
...
l.lock();// 手动上锁
try{ // 使用try-finally的写法是为了一定执行unlock方法,否则出现意外如exception,那么可以不会unlock,就出问题了
...
}finally{
// 手动解锁
l.unlock();
}
...
JDK5之前,同步还可以添加在方法之上。
如果一个方法中的所有代码均需同步,则可以将同步直接添加在方法上。
同步代码块:
synchronized(锁){
…
}
同步方法:
public synchronized void 方法(){
…
}
问题来了同步方法上的锁在哪?
能不能将同步添加在run方法上?
答:语法上讲上可以的,让整个run方法同步。但实际上不允许,因为run方法是线程要执行的任务方法,线程只有进入run方法中才能执行任务,而将同步添加在run方法上,意味着线程无法进入run方法,更无法执行任务。
举个例子,十个人搬水,有一个人去搬了,其他人由于锁都只能等着,那么等于没有进行多线程。
死锁可能出现下几种现象:
常见死锁线程:
2线程执行一个任务,但获取锁的方式不同:
Thread-0 需要获取A锁再获取B锁;Thread-1 需要先获取B锁再获取A锁;
尽可能在开发中不使用嵌套,也不使用多个锁保证同步
字符串缓冲区:
集合中:
thread.setPriority(num)
可以修改线程的优先级,num为设定的优先级可以将不同线程划分如不同组来统一管理,可以在创建 thread 的时候指定其所属的线程组
守护线程又名用户线程/后台线程,依然是执行任务的,但是需要依赖于某个非守护线程。如果程序中非守护线程均结束任务,那么无论守护线程任务执行如何,均停止执行。
形象例子:白雪公主死了,小矮人就没必要活了,都得死
守护线程 new 的线程一定是守护线程
非守护线程 new 的线程一定是非守护线程
thread.isDaemon() 判断是否是守护线程
thread.setDaemon() 将thread设置为守护线程
thread.interrupt()
终止当前线程的 wait/sleep状态thread.join()
执行该代码的线程会等 thread 线程任务完成后再执行Thread.yield()
静态方法,线程执行该代码时,会暂停,然后立刻恢复到抢夺Cpu执行的状态thread.sleep( int num)
使线程休眠 num 毫秒,开发尽量不使用可以让程序在指定时间执行任务/重复执行某些代码
Timer t = new Timer();
t.schedule(task, num);// task为所要执行的任务,是TimerTask对象, num为时间;
// task可以使用匿名类的方法
t.schedule(new TimerTask(){
public void run(){
...// 复写run方法
}
}, 1000);