4-java多线程-基础多线程知识

java多线程-基础多线程知识

文章目录

  • java多线程-基础多线程知识
  • 一、搬水问题
  • 二、创建线程的方法
    • 1.将类声明为Thread的子类
    • 2. 类实现Runnable接口
  • 三、线程其他技术
    • 1.同步的锁
    • 2.面试题
    • 3. 死锁
    • 4. 线程安全相关的类
    • 5. 线程优先级
    • 6. 线程组
    • 7. 守护线程
    • 8. 线程中的其他方法
    • 9. 定时器


一、搬水问题

搬水问题,现在有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接口)

1.将类声明为Thread的子类

将所需要的的线程类声明为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();
	}
}

运行结果正常。

2. 类实现Runnable接口

将线程类实现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.同步的锁

分为两类: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 方法(){

}
问题来了同步方法上的在哪?

  • 非静态的同步方法使用的锁就是当前调用这个方法的对象:this。
  • 静态的同步方法使用的锁是class文件(类名.class)

2.面试题

能不能将同步添加在run方法上?
答:语法上讲上可以的,让整个run方法同步。但实际上不允许,因为run方法是线程要执行的任务方法,线程只有进入run方法中才能执行任务,而将同步添加在run方法上,意味着线程无法进入run方法,更无法执行任务。
举个例子,十个人搬水,有一个人去搬了,其他人由于锁都只能等着,那么等于没有进行多线程。

3. 死锁

死锁可能出现下几种现象:

  1. 线程获取锁的时候,锁被其他线程持有,而其他线程也无法执行某些动作导致程序卡死
  2. 所有线程都wait了,没有活的线程

常见死锁线程:
2线程执行一个任务,但获取锁的方式不同:
Thread-0 需要获取A锁再获取B锁;Thread-1 需要先获取B锁再获取A锁;

尽可能在开发中不使用嵌套,也不使用多个锁保证同步

4. 线程安全相关的类

字符串缓冲区:

  • StringBuffer: 线程安全
  • StringBuilder : 不安全,效率高

集合中:

  • JDK1.2后的所有集合,均不安全
  • Vector、HashTable 是1.0时期的集合,安全

5. 线程优先级

  • 优先级高的线程先执行的概率高,从1~10依次升高,1最低,5默认,10最大。
  • 子线程的默认优先级等于父线程的当前优先级
  • thread.setPriority(num)可以修改线程的优先级,num为设定的优先级
  • 一般设置时,会将不同优先级的彼此区分大一些,常用的就是1、5、10

6. 线程组

可以将不同线程划分如不同组来统一管理,可以在创建 thread 的时候指定其所属的线程组

7. 守护线程

守护线程又名用户线程/后台线程,依然是执行任务的,但是需要依赖于某个非守护线程。如果程序中非守护线程均结束任务,那么无论守护线程任务执行如何,均停止执行。
形象例子:白雪公主死了,小矮人就没必要活了,都得死

守护线程 new 的线程一定是守护线程
非守护线程 new 的线程一定是非守护线程

thread.isDaemon() 判断是否是守护线程
thread.setDaemon() 将thread设置为守护线程

8. 线程中的其他方法

  • thread.interrupt() 终止当前线程的 wait/sleep状态
  • thread.join() 执行该代码的线程会等 thread 线程任务完成后再执行
  • Thread.yield() 静态方法,线程执行该代码时,会暂停,然后立刻恢复到抢夺Cpu执行的状态
  • thread.sleep( int num) 使线程休眠 num 毫秒,开发尽量不使用

9. 定时器

可以让程序在指定时间执行任务/重复执行某些代码

Timer t = new Timer();
t.schedule(task, num);// task为所要执行的任务,是TimerTask对象, num为时间;
// task可以使用匿名类的方法
t.schedule(new TimerTask(){
	public void run(){
		...// 复写run方法
	}
}1000);

你可能感兴趣的:(java,java基础,java,多线程)