多线程的概念
进程、线程、多线程的概念
进程:
正在进行中的程序
线程:进程中一个负责程序执行的控制单元(执行路径)。一个进程中可以有多个执行路径,称之为多线程。一个进程中至少要有一个线程。
优点:解决了多部分代码同时运行的问题。
弊端:线程太多,会导致效率的降低。
Java VM启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。jvm启动不止一个线程,还有负责垃圾回收机制的线程。
所以JVM启动时,至少有两个线程:
1.执行main函数的线程,该线程的任务代码都定义在main函数中。
2.负责垃圾回收的线程。
创建线程方式一
步骤:
1.继承Thread类
2.重写Thread类中的run方法(Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码 。该存储功能就是run方法也就是说Thread类中的run方法,用于 存储线程要运行的代码)
3.调用线程的start方法,该方法有两个作用,启动线程和调用run方法
2.重写Thread类中的run方法(Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码 。该存储功能就是run方法也就是说Thread类中的run方法,用于 存储线程要运行的代码)
3.调用线程的start方法,该方法有两个作用,启动线程和调用run方法
class TestWork {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
t.start(); //调用start()方法,启动线程
for(int x = 0 ; x < 60 ; x++) {
System.out.println("main......."+x);
}
}
}
class ThreadDemo extends Thread {
//继承Thread类时,要重写run方法
public void run() {
for(int x = 0 ; x < 60 ; x++) {
System.out.println("Thread......."+x);
}
}
}
运行结果每一次都不同?
多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序正在运行。(多核除外)cpu在做着快速切换,已达到看上去是同时运行的效果。这就是多线程的一个
特性,随机性。
线程都有自己默认的名称,Thread-编号,编号从0开始
static Thread currentThread():获取当前线程对象
getName():获取线程名称
class TestWork {
public static void main(String[] args) {
ThreadDemo t1 = new ThreadDemo("Thread--1");
ThreadDemo t2 = new ThreadDemo("Thread--2");
t1.start(); //调用start()方法,启动线程
t2.start();
for(int x = 0 ; x < 60 ; x++) {
System.out.println("main......."+x);
}
}
}
class ThreadDemo extends Thread {
private String name; //给线程自定义一个名字
ThreadDemo(String name) {
super(name); //Thread类里面定义好了构造方法直接调用
}
public void run() {
for(int x = 0 ; x < 40 ; x++)
System.out.println(this.getName()+":"+x); //获取线程名字的方法等同于Thread.currentThread().getName()
}
}
创建线程方式二
步骤:
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数(自定义的run方法所属对象是Runnable接口的子类对象 ,要让让线程去执行指定对象的run方法,就 必须明确该run方法所属对象)
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数(自定义的run方法所属对象是Runnable接口的子类对象 ,要让让线程去执行指定对象的run方法,就 必须明确该run方法所属对象)
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
优点:避免了单继承的局限性,将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
class TestWork {
public static void main(String[] args) {
ThreadDemo son = new ThreadDemo();
//将Runnable接口的子类对象传递给Thread类的构造函数
Thread t1 = new Thread(son);
Thread t2 = new Thread(son);
Thread t3 = new Thread(son);
Thread t4 = new Thread(son);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class ThreadDemo implements Runnable {
private int num = 100; //定义一个变量并赋值100,
public void run() {
while(true) {
if(num > 0) // 对变量进行判断,大于0则执行下面的代码
System.out.println(Thread.currentThread().getName()+"--num"+num--); //num变量赋值后减1
}
}
}
线程安全问题
线程安全问题产生的原因
class TestWork {
public static void main(String[] args) {
ThreadDemo son = new ThreadDemo();
//将Runnable接口的子类对象传递给Thread类的构造函数
Thread t1 = new Thread(son);
Thread t2 = new Thread(son);
Thread t3 = new Thread(son);
Thread t4 = new Thread(son);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class ThreadDemo implements Runnable {
private int num = 100;
public void run() {
while(true) {
if(num > 0) {
try {
Thread.sleep(10); //调用Thread类里面的sleep方法,让线程等待一段时间。
} catch (InterruptedException e) { //sleep方法声明了异常,但是ThreadDemo的父类没有声明,所以这里这能try/catch
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--num"+num--);
}
}
}
}
通过对线程创建方式二里的程序多次执行发现,出现了不同线程输出num为负数的情况。因为当线程2执行num--之前,线程0,1,3都通过了if的判断,此时此刻拿着num为2的线程2执行num--后,其它线程开始一次执行,所以就出现了为负数的情况。
线程安全问题产生的原因:
当多条语句在操作同一个线程共享的数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就已经参与了进来,导致共享 数据的错误
解决方案
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行
Java对于多线程的安全问题提供了专业的解决方法,同步代码块。
格式:
synchronized(对象) {
需要被同步的代码
}
需要被同步的代码
}
优点:解决了多线程的安全问题
缺点:多线程比较多时,较为消耗资源,每个线程都需要判断
缺点:多线程比较多时,较为消耗资源,每个线程都需要判断
注意:使用同步代码块必须要有两个或者以上的线程,必须多个线程使用同一个锁
class TestWork {
public static void main(String[] args) {
ThreadDemo son = new ThreadDemo();
//将Runnable接口的子类对象传递给Thread类的构造函数
Thread t1 = new Thread(son);
Thread t2 = new Thread(son);
Thread t3 = new Thread(son);
Thread t4 = new Thread(son);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class ThreadDemo implements Runnable {
private int num = 100;
Object obj = new Object(); //建立一个同步代码块锁
public void run() {
while(true) {
//同步代码被多个线程共享的部分
synchronized(obj) {
if(num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--num"+num--);
}
}
}
}
}
因为有同步代码块的原因,先抢到cpu执行权的线程,进来。其它的只能等进锁的线程执行完才能进去。
创建一个银行程序,有两个储户往银行里面存钱,分别存300,每次100,存3次。
1.明确哪些代码是多线程运行代码
2.明确共享数据
3.明确多线程运行代码中哪些语句是操作共享数据的
class TestWork {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
//开启两个线程
t1.start();
t2.start();
}
}
class Bank {
private int sum;
Object obj = new Object();
//创建一个存钱的方法
public void add(int m) {
synchronized(obj) {
sum = sum +m;
System.out.println(Thread.currentThread().getName()+"--money"+sum);
}
}
}
class Cus implements Runnable {
private Bank b = new Bank(); //创建对象,以便在run方法里面调用存钱的方法
public void run() {
//用for循环来设定往里存放数据的次数
for(int x = 0 ; x < 3 ; x++) {
b.add(100);
}
}
}
该程序还能再到函数上加上synchronized修饰符,因为整个函数都是共享数据,称之为同步函数
public synchronized void add(int m) {
//synchronized(obj) {
sum = sum +m;
System.out.println(Thread.currentThread().getName()+"--money"+sum);
//}
}
同步函数和同步代码块的区别:
1.同步函数的锁是固定的this。
2.同步代码块的锁是任意的对象。
2.同步代码块的锁是任意的对象。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,可以实现同步
class TestWork {
public static void main(String[] args) {
Ticket1 c = new Ticket1();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
try {
Thread.sleep(10); //让主线程等待,因为t1有可能开启了之后没有抢到cpu执行权,让主线程继续往下执行把flag改为flase,以至于两个线程还是用同一个同步代码块。
}catch(Exception e) {
}
c.setBool(false); //把标记改为false让t2区执行同步函数
t2.start();
}
}
class Ticket1 implements Runnable {
private int tick = 100;
private boolean flag = true; //定义一个标记变量,让t1和t2分道扬镳
Object obj = new Object();
public void setBool(boolean flag) {
this.flag = flag;
}
public void run() {
if(flag) {
while(true) {
synchronized(this) {
if(tick > 0) {
try {
Thread.sleep(10);
}catch(Exception e) {
}
System.out.println(Thread.currentThread().getName()+":ticket--"+tick--);
}
}
}
}else {
while(true)
show();
}
}
//定义一个同步函数,让t2进来
public synchronized void show() {
if(tick > 0) {
try {
Thread.sleep(10);
}catch(Exception e) {
}
System.out.println(Thread.currentThread().getName()+":ticket--"+tick--);
}
}
}
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
多线程下的单例设计模式
单例设计模式下的懒汉式存在安全问题,可以使用同步函数解决,但是synchronized Single getInstance()这种形式使用起来效率太低,如下改动,即可太高效率
class Single {
private Single() {
}
private static Single s = null;
public static Single getInstance() {
if(s == null) { //如果对象已经存在了,就能直接获取不需要再经过下面的代码,提高效率
synchronized(Single.class) { //因为存在都通过第一个if判断的情况,那么会建立多个对象。所以必须加锁
if(s == null)
s = new Single();
}
}
return s;
}
}
死锁
同步中嵌套同步,如下示例
class DeadLock implements Runnable {
private boolean flag;
DeadLock(boolean flag) {
this.flag = flag;
}
public void run() {
//锁里面还有一把锁
if(flag) {
synchronized(Lock.l1) {
System.out.println(Thread.currentThread().getName()+"--l1");
synchronized(Lock.l2) {
System.out.println(Thread.currentThread().getName()+"--l2");
}
}
} else {
synchronized(Lock.l2) {
System.out.println(Thread.currentThread().getName()+"--l2");
synchronized(Lock.l1) {
System.out.println(Thread.currentThread().getName()+"--l1");
}
}
}
}
}
//定义两把不同的锁,方便使用
class Lock {
static Lock l1 = new Lock();
static Lock l2 = new Lock();
}
class Synchronized {
public static void main(String[] args) {
DeadLock d1 = new DeadLock(true);
DeadLock d2 = new DeadLock(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
为什么会造成死锁现象?
线程t1执行l2里面的代码,必须要拿到l2;线程t2要执行l1里面的代码,必须要拿到l1;当线程开启,t1首先拿到l1,t2则拿到l2,所以t1进入不到l2,t2页进入不到l1。
线程间通信
线程间通信涉及到的方法
线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
等待/唤醒机制涉及方法:
wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中
notify(): 唤醒线程池中的一个线程(任何一个都有可能)
notifyAll():唤醒线程池中的所有线程。
注意:
都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,只有同步才具有锁。
为什么这些操作线程的方法定义在Object类?
因为这些方法在操作同步中线程时,都必须要标识他们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。也就是说,等待和唤醒必须是同一个锁而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
class Resource {
private String name;
private String sex;
private boolean flag = false; //定义一个标记用来判断线程是否要等待
public synchronized void set(String name,String sex) {
if(flag) //如果flag为true则输入线程等待
try {
this.wait();
} catch(Exception e) {
}
this.name = name;
this.sex = sex;
flag = true; //当线程输入了资源后,把标记改为true让输出线程输出资源
this.notify();
}
public synchronized void out() {
if(!flag) //如果!flag是为假则线程等待
try {
this.wait();
}catch(Exception e) {
}
System.out.println("name:"+name+" -- sex:"+sex);
flag = false; //输出完资源之后把flag改为fale
this.notify(); //唤醒正在等待的输入线程。
}
}
class Input implements Runnable {
private Resource r; //定义一个引用r
Input(Resource r) {
this.r = r;
}
public void run() {
int x = 0;
while(true) {
//循环输入两个组不同的数据
if(x == 0) {
r.set("Jack", "男");
}else {
r.set("Moli", "女");
}
x = (x+1)%2;
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class TestWork {
public static void main(String[] args) {
Resource r = new Resource(); //创建资源对象
Input in = new Input(r); //资源对象作为参数,传入
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
多个生产者以及 消费者的问题:
class Resource {
private String name;
private int count;
private boolean flag = false; //定义一个标记用来判断线程是否要等待
public synchronized void set(String name) {
if(flag) //如果flag为true则输入线程等待
try {
this.wait();
} catch(Exception e) {
}
this.name = name+count++;
System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);
flag = true; //当线程输入了资源后,把标记改为true让输出线程输出资源
this.notify();
}
public synchronized void out() {
if(!flag) //如果!flag是为假则线程等待
try {
this.wait();
}catch(Exception e) {
}
System.out.println(Thread.currentThread().getName()+"--消费者--"+this.name);
flag = false; //输出完资源之后把flag改为fale
this.notify(); //唤醒正在等待的输入线程。
}
}
class Input implements Runnable {
private Resource r; //定义一个引用r
Input(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.set("啤酒");
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class TestWork {
public static void main(String[] args) {
Resource r = new Resource(); //创建资源对象
Input in = new Input(r); //资源对象作为参数,传入
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(in);
Thread t3 = new Thread(out);
Thread t4 = new Thread(out);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
为什么有同步锁,有wait(),notify()方法还是出现了安全问题?
假设:
假设:
1.线程Thread-0获取到cpu执行权进入到锁,生产了啤酒1,将flag设置为true,重新获取到执行权回到if判断语句。由于flag已变为ture,所以执行wait方法。然后Thread-1获取到CPU执行权,同样要执行wait方法。
2.线程Thread-2获取到cpu执行权进入到锁,由于flag为true,所以消费了啤酒1,把flag改为false,然后唤醒了Thread-0,但是没有获取到执行权。回到if判断语句,!false为真, 所以Thread-2执行wait方法。接着线程Thread-3获取到了CPU执行权,同样也被wait了。
3.唤醒的Thread-0因为已经被if语句判断过了,所以直接往下面执行代码,生产啤酒2,唤醒Thread-1。同理,Thread-也直接生产啤酒3,唤醒了Thread-2,导致啤酒2没被消费。
4.以此类推,接来下被唤醒的Thread-2消费了,啤酒3。Thread-3也消费了啤酒3。
If判断语句只能判断以此,故用while()循环判断。
注意:
由上面的“假设”可以推断,在while()循环中会出现死锁的情况,故把notify()改为notifyAll()方法
class Resource {
private String name;
private int count;
private boolean flag = false; //定义一个标记用来判断线程是否要等待
public synchronized void set(String name) {
while(flag) //如果flag为true则输入线程等待
try {
this.wait();
} catch(Exception e) {
}
this.name = name+count++;
System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);
flag = true; //当线程输入了资源后,把标记改为true让输出线程输出资源
this.notifyAll(); //唤醒所有等待的线程,防止4个线程都出现等待的状况
}
public synchronized void out() {
while(!flag) //如果!flag是为假则线程等待
try {
this.wait();
}catch(Exception e) {
}
System.out.println(Thread.currentThread().getName()+"--消费者--"+this.name);
flag = false; //输出完资源之后把flag改为fale
this.notifyAll();
}
}
class Input implements Runnable {
private Resource r; //定义一个引用r
Input(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.set("啤酒");
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class TestWork {
public static void main(String[] args) {
Resource r = new Resource(); //创建资源对象
Input in = new Input(r); //资源对象作为参数,传入
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(in);
Thread t3 = new Thread(out);
Thread t4 = new Thread(out);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
JDK1.5新特性
JDK1.5 中提供了多线程升级解决方案:
Lock接口:替代了同步代码块或者同步函数
lock():获取锁
unlock():释放锁,为了防止异常出现,导致锁无法关闭,所以锁的关闭动作要放在finally中
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些解释器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
await()对应wait()
singal()对应notify()
singalAll()对应notifyAll()
import java.util.concurrent.locks.*;
class Resource {
private String name;
private int count;
private boolean flag = false; //定义一个标记用来判断线程是否要等待
private Lock lock = new ReentrantLock(); //new一个对象,ReentrantLock()是Lock的子类
private Condition con_in = lock.newCondition(); //创建Condition类对象,并且一个Lock可以对应多个condition对象
private Condition con_ou = lock.newCondition();
public void set(String name) throws Exception {
lock.lock();
try {
while(flag) //如果flag为true则输入线程等待
con_in.await();
this.name = name+count++;
System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);
flag = true; //当线程输入了资源后,把标记改为true让输出线程输出资源
con_ou.signal();
}finally {
lock.unlock(); //释放锁是必须要关闭的资源
}
}
public void out() throws Exception {
lock.lock();
try {
while(!flag) //如果!flag是为假则线程等待
con_ou.await();
System.out.println(Thread.currentThread().getName()+"--消费者--"+this.name);
flag = false; //输出完资源之后把flag改为fale
con_in.signal(); //唤醒正在等待的输入线程。
} finally {
lock.unlock();
}
}
}
class Input implements Runnable {
private Resource r; //定义一个引用r
Input(Resource r) {
this.r = r;
}
public void run() {
while(true) {
try {
r.set("啤酒");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) {
this.r = r;
}
public void run() {
while(true) {
try {
r.out();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class TestWork {
public static void main(String[] args) {
Resource r = new Resource(); //创建资源对象
Input in = new Input(r); //资源对象作为参数,传入
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(in);
Thread t3 = new Thread(out);
Thread t4 = new Thread(out);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
停止线程
任务中都会有循环接口,控制住循环就可以结束任务,控制循环通常用定义标记来完成。stop方法已经过时,不再使用。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();
class StopThread implements Runnable {
private boolean flag = true;
public synchronized void run() {
while(flag) {
try {
wait();
} catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+"正在运行");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"正在运行"+num);
}
}
}
线程类的其它方法
setDaemon():将调用此方法的线程标记为守护线程,该方法必须在启动线程前调用。
join(): 等待调用此方法的线程结束
setPriority(): 设置此线程的优先级可以加参数(MAX_PRIORITY)
toStirng(): 返回该线程的字符串表现形式,包括线程名称,优先级,和线程组。
yield(): 暂停当前正在执行的线程对象,并执行其他线程
class Demo implements Runnable {
public void run() {
for(int x=0; x<70; x++) {
System.out.println(Thread.currentThread().toString()+"....."+x);
Thread.yield();
}
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//t1.setPriority(Thread.MAX_PRIORITY); //将t1的优先级提高的最大
t2.start();
//t1.join(); //如果在这里调用了join()方法,则main线程会停下来
for(int x=0; x<80; x++) {
//System.out.println("main....."+x);
}
System.out.println("over");
}
}