线程简介
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
线程在控制着进程的执行。
我们可以形象把多线程的运行行为在互相抢夺cpu执行权。
这就是多线程的随机性。
如何创建一个线程
步骤:
1,定义类继承Thread.
2,复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3,调用线程的start方法。该方法有两个作用。
启动线程,调用run方法。
为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
run方法是用于存储线程要运行的代码。
public class Demo {
public static void main(String[] args) {
/*
* 创建线程的目的就是为了开启一条执行路径,去运行的代码和其他代码实现同时运行
*
* 而运行的代码就是这个执行路径的任务
*
* jvm创建的主线程的任务都定义在了主函数中。
*
* 而自定义的线程它的任务在哪呢? Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务的描述
* 这个任务就是通过Thread类的run方法来体现的, 也就是说,run方法封装自定义线程运行任务的函数
*
* run方法中定义就是线程要运行的任务代码
*
* 开启线程就是运行指定代码,所以只有继承Thread类,并复写run方法。 将运行的代码定义在run方法中即可
*/
Demo4 d4 = new Demo4("周珂珂");
Demo4 d5 = new Demo4("张三");
d4.start();// 开启线程调用run方法
d5.start();
System.out.println("结束了这个线程。。" + Thread.currentThread().getName());
}
}
/**
* 继承方式
* @author Administrator
*
*/
class Demo4 extends Thread {
private String name;
Demo4(String name) {
super(name);//给定义的线程命名
}
//重写run方法
public void run() {
//循环测试
for (int x = 0; x < 10; x++) {
System.out.println(name + ".." + x + "..."
+ Thread.currentThread().getName());
}
}
}
线程运行状态
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
没有执行资格的情况下是冻结状态。sleep(), 时间到 wait(),notify()
有执行资格的状态叫做临时状态。
既有资格又有执行权运行状态。
线程对象以及名称
原来线程都有自己默认的名称。
Thread-编号 该编号从0开始。
Thread 对象的setName() getName();方法
线程初始化名称:构造方法 super(name);
Thread.currentThread();返回对当前正在执行的线程对象的引用。
创建线程方法之二
/*
* 创建线程的第二种方式:实现Runnable接口
* 1,定义类实现Runnable接口
* 2,覆盖接口中的run方法,将线程的任务代码封装到run方法中
* 3,通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
* 为什么呢?因为线程的任务都封装在Runnable接口子类对象的run方法中。
* 所以要线程对象的start方法开启线程
* 4,调用线程对象的start方法开启线程
*
* 实现Runnable接口的好处:
* 1,将线程的任务从线程的子类中分离出来,进行单独的封装
* 按照面向对象的思想将任务封装成对象
* 2,避免了java单继承的局限性
* 所以创建线程的第二种方式较为常用
*/
public class Test9 {
public static void main(String[] args) {
TextThread t = new TextThread();
Thread t1 = new Thread(t);//线程1
Thread t2 = new Thread(t);//线程2
t1.start();
t2.start();
}
}
/**
* 继承方式,实现Runnable接口
* @author Administrator
*
*/
class TextThread implements Runnable {
//重写run方法
@Override
public void run() {
// TODO Auto-generated method stub
show();
}
//测试方法
public void show() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
实现方式好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。
两种方式的区别:
继承Thread:线程代码存放在Thread子类run方法中
实现Runnnable,线程代码存在接口子类的run方法。
线程的安全问题
通过分析,发现,打印出0,-1,-2等错票
public class Test9 {
public static void main(String[] args) {
Ticket1 t = new Ticket1();
//开启四个线程
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket1 implements Runnable {
private int ticket = 100;//共享数据
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sale"
+ ticket--);//有负数的存在,安全隐患
}
}
}
}
以上多线程的运行出现了安全问题。
问题原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。
导致共享数据的错我。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
java对于多线程的安全问题提供了专业的解决方式。
就是同步代码块。 哪些代码需要同步,就看哪些语句在操作共享数据。
synchronized(对象){
需要被同步的代码
}
如下面的解决方案。
public class Test9 {
public static void main(String[] args) {
//开启4个线程
Ticket1 t = new Ticket1();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket1 implements Runnable {
private int ticket = 1000;
// Object obj=new Object();
public void run() {
while (true) {
//同步代码块,加锁 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。、
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(10);//让线程休眠
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "sale" + ticket--);//没有出现负数票
}
}
}
}
}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
火车上的卫生间
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。允许消耗范围内的。
同步有两种表现形式,第一个是同步代码块,第二是同步函数。把synchronized作为修饰符放在函数上。
public class Test9 {
public static void main(String[] args) {
//开启4个线程
Ticket1 t = new Ticket1();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket1 implements Runnable {
private int ticket = 1000;
// Object obj=new Object();
public void run() {
while (true) {
//同步代码块,加锁 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。、
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(10);//让线程休眠
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "sale" + ticket--);//没有出现负数票
}
}
}
}
}
public class Test10 {
public static void main(String[] args) {
Tickets t=new Tickets();
//创建4个线程卖票,并开启
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Tickets implements Runnable{
private int tick=1000;//票数
//复写run方法调用show
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
this.show();
}
}
//同步函数所持有的锁是this
public synchronized void show(){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
同步函数用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属的对象,就是this.
同步函数的使用的锁是this
验证:使用两个线程来买票。
一个线程在同步代码块中。
一个线程在同步函数中。
都在执行买票动作。
public class Test10 {
public static void main(String[] args) {
Tickets t = new Tickets();
// 创建4个线程卖票,并开启
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
/*
* Thread t3=new Thread(t); Thread t4=new Thread(t);
*/
t1.start();// t1一开启跑到同步代码块中。,开启这个线程不一定立即执行。处于临时状态,有可能执行下面一句
t.flag = false;// 在t2开启之前,把标识变为false;
try {
Thread.sleep(10);// 主线程停止10毫秒,只能是t1在运行。过了时间段,可能执行下面的语句
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();// t2一开启跑到同步函数中。
/*
* t3.start(); t4.start();
*/
}
}
class Tickets implements Runnable {
private int tick = 1000;// 票数
// 复写run方法调用show
Object obj = new Object();
boolean flag = true;
@Override
public void run() {
// TODO Auto-generated method stub
if (flag) {
while (true) {
// 同步代码块
// synchronized(obj),存在安全问题
synchronized (this) {
if (tick > 0) {
try {
Thread.sleep(10);
System.out.println("同步代码块");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
} else {
while (true) {
show();
}
}
}
// 同步函数
public synchronized void show() {
if (tick > 0) {
try {
Thread.sleep(10);
System.out.println("同步函数");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
同步代码块使用的是任意的对象
如果同步函数被静态修饰后,使用的锁是什么呢?
不再是this了。因为静态方法也不可以定义this.
静态进内存,内存中没有本类对象,但是一定有该类对应对应的字节码对象。
类名.class 该对象的类型是Class.
静态同步函数的锁:
静态的同步方法使用的锁是该方法所在类的字节码对象。
验证方法如下:
public class Test10 {
public static void main(String[] args) {
Tickets t = new Tickets();
// 创建4个线程卖票,并开启
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
/*
* Thread t3=new Thread(t); Thread t4=new Thread(t);
*/
t1.start();// t1一开启跑到同步代码块中。,开启这个线程不一定立即执行。处于临时状态,有可能执行下面一句
t.flag = false;// 在t2开启之前,把标识变为false;
try {
Thread.sleep(10);// 主线程停止10毫秒,只能是t1在运行。过了时间段,可能执行下面的语句
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();// t2一开启跑到同步函数中。
/*
* t3.start(); t4.start();
*/
}
}
class Tickets implements Runnable {
private static int tick = 1000;// 票数
// 复写run方法调用show
Object obj = new Object();
boolean flag = true;
@Override
public void run() {
// TODO Auto-generated method stub
if (flag) {
while (true) {
// 同步代码块
// synchronized(obj),存在安全问题
synchronized (Test10.class) {
if (tick > 0) {
try {
Thread.sleep(10);
System.out.println("同步代码块");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
} else {
while (true) {
show();
}
}
}
// 同步函数
public synchronized void show() {
if (tick > 0) {
try {
Thread.sleep(10);
System.out.println("同步函数");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
懒汉式单例模式
public class Test10 {
public static void main(String[] args) {
System.out.println("hello");
}
}
class SingleDemo {
private static SingleDemo s = null;// 共享数据,多个线程并发访问getInstance(),有可能存在安全问题,多条语句操作
private SingleDemo() {// 私有构造函数
}
public static SingleDemo getInstance() {
if (s == null) {
synchronized (SingleDemo.class) {// 锁是字节码文件对象
if (s == null) {
s = new SingleDemo();// 对象延迟加载
}
}
}
return s;
}
}
以上饿汉式不说,单说懒汉式。
多个线程并发访问getInstance(), 多条语句操作共享数据s,存在安全隐患。
懒汉式:实例延迟加载,多线程访问存在安全隐患。加同步来解决,加同步函数,和同步代码块都行。但是稍微有些低效,
用双重判断可以解决效率问题。加同步使用的锁是哪个,该类所属的字节码对象
线程死锁的案例
两个对象互相依赖,所以死锁!示例代码如下:
public class Test10 implements Runnable {
public int flag = 1;
static Object o1 = new 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) {
Test10 td1 = new Test10();
Test10 td2 = new Test10();
//定义标识
td1.flag = 1;
td2.flag = 0;
//两个线程开启
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
}
线程间的通讯:
其实就是多个线程在操作同一个资源,
但是操作的动作不同。
//资源
class Res {
String name;
String sex;
}
//添加的方法
class Input implements Runnable {
private Res r;
public Input(Res r) {
this.r = r;
}
public void run() {
// TODO Auto-generated method stub
int x = 0;//标识变量如果是0添加女,如果是1,添加男
while (true) {
synchronized (r) {//同步代码块添加,不加同步执行权可能被输出抢走。锁可以是资源对象。
if (x == 0) {
r.name = "丽丽";
r.sex = "女";
} else {
r.name = "mike";
r.sex = "man";
}
x = (x + 1) % 2;
}
}
}
}
//输出类
class Output implements Runnable {
private Res r;//资源对象
public Output(Res r) {
this.r = r;
}
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (r) {//这里也必须要加线程,操作共享数据,同一个锁,可以是资源对象
System.out.println(r.name + "..." + r.sex);//输出是统一资源
}
}
}
}
public class Test11 {
public static void main(String[] args) {
//形象的比喻 有一堆煤,有两个大卡车,一个进的,一个出的,把煤放到大卡车上,把卡车放到高速公路上。
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
//开启两个线程
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
以上的代码还是有问题的,按理说应该是存一个打印一个这样是比较靠谱的。上面的情况是一大片一大片的男,或者女。为什么出现这种情况?
输入的线程如果获得了cpu执行权,它存了一个值后,其他线程进不来,这个时候出了同步,output,input都有可能抢到cpu执行权。所以输入有可能还会抢到,前面的值就回被覆盖掉了。当某一时刻,执行权被抢走了,输出被抢到了,他也可能把一个值打印多遍,所以造成了上面的情况。cpu切换造成的。现在需求是这样的,添加一个,取出一个,这样才是最靠谱的。为了满足条件需求,我们要做的是,在资源中加入一个标记,默认false;输入线程在往里面添加数据时,判断标记,false则存入,存完后,输入线程可能还持有执行权,将标记改为真,代表里面有数据了。为true时,不能在存入了,这个时候,让输入线程等着不动,wait()放弃了执行资格;当取走了之后,才能醒,notify()。
当output具备执行权的时候,开始输出,之前也要进行判断,如果true,取出,打印,变为false还持有执行权,回来之后,为false,wati(),叫醒 input,input等的时候,再把output叫醒。等待唤醒机制。
wait();
notity();
notityAll();
都是用在同步中。因为要对持有监视器(锁)的线程操作。
所以要使用同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object中呢?
因为这些方法在操作同步线程时,都必须要标识它们所操作线程只有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上notity唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
class Res {
String name;
String sex;
boolean flag = false;// 标记是否有 资源
}
//添加类,实现Runnable接口
class Input implements Runnable {
private Res r;//资源对象
public Input(Res r) {// 关联资源对象
this.r = r;
}
//重写run方法
public void run() {
// TODO Auto-generated method stub
int x = 0;// 这里也必须要加线程,操作共享数据,同一个锁,可以是资源对象
while (true) {
synchronized (r) {
if (r.flag) {
try {
r.wait();// 等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (x == 0) {
r.name = "丽丽";
r.sex = "女";
} else {
r.name = "mike";
r.sex = "man";// 线程结束后,有可能还能抢到cpu执行权
}
x = (x + 1) % 2;
r.flag = true;
r.notify();// 唤醒线程池中的最早wait的线程。
}
}
}
}
//输出类实现Runnable接口
class Output implements Runnable {
private Res r;
public Output(Res r) {//关联资源对象
this.r = r;
}
//重写run方法
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (r) {
if (!r.flag) {
try {
r.wait();// 等待,取消了执行资格
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(r.name + "..." + r.sex);
r.flag = false;
r.notify();// 叫醒线程池中的最早线程
}
}
}
}
public class Test {
public static void main(String[] args) {
Res r = new Res();
// 形象的比喻 有一堆煤,有两个大卡车,一个进的,一个出的,把煤放到大卡车上,把卡车放到高速公路上。
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 Res {
private String name;
private String sex;
boolean flag = false;
//设置添加方法
public synchronized void set(String name, String sex) {
if (flag) {
try {
this.wait();//线程等待,没有执行资格
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;//有了数据,标识变为true;
this.notify();//唤醒线程池中的最早wait的线程
}
//输出方法
public synchronized void out() {
if (!flag) {
try {
this.wait();//线程等待,没有执行资格
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name + ".." + sex);//打印
flag = false;//取走了数据,变为false;
this.notify();//唤醒线程池中的最早wait的线程
}
}
//添加类,input
class Input implements Runnable {
private Res r;
public Input(Res r) {//关联资源
this.r = r;
}
//重写run方法
public void run() {
// TODO Auto-generated method stub
int x = 0;
while (true) {
if (x == 0) {
r.set("mike", "man");
} else {
r.set("丽丽", "女");
}
x = (x + 1) % 2;
}
}
}
//输出类
class Output implements Runnable {
private Res r;
public Output(Res r) {//关联资源
this.r = r;
}
public void run() {
// TODO Auto-generated method stub
while (true) {
r.out();
}
}
}
public class Test {
public static void main(String[] args) {
Res r = new Res();//资源对象
//创建两个线程,并开启
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
生产者与消费者的例子。出现更多线程运行程序。以上的例子会出现问题。
public class Test {
public static void main(String[] args) {
Resource r = new Resource();//创建资源对象
Producer pro = new Producer(r);//生成者
Cousumer con = new Cousumer(r);//消费者
//创建4个对象,并开启
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//资源类
class Resource {
private String name;
private int count = 1;// 编号
private boolean flag = false;//判断标记
//赋值方法给 添加资源时调用
public synchronized void set(String name) {
while (flag) {
try {
this.wait();//线程等待,不具执行资格了。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "............生产者"
+ this.name);
flag = true;//输出了之后,没有资源了,标记改为true;
this.notifyAll();//唤醒线程池全部线程
}
public synchronized void out() {
while (!flag) {
try {
this.wait();线程等待,不具执行资格了。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "..消费者"
+ this.name);
flag = false;//输出了之后,没有资源了,标记改为false;
this.notifyAll();//线程等待,不具执行资格了。
}
}
//生产者类,添加商品
class Producer implements Runnable {
private Resource res;
public Producer(Resource res) {//生成者关联资源对象
this.res = res;
}
//重写run方法。
public void run() {
// TODO Auto-generated method stub
while (true) {
res.set("+商品+");//赋值,添加产品
}
}
}
//消费者类,取走商品
class Cousumer implements Runnable {// 消费者
private Resource res;
public Cousumer(Resource res) {
this.res = res;
}
public void run() {
// TODO Auto-generated method stub
while(true){
res.out();//输出
}
}
}
当出现多个生产者消费的时候,必须要有while循环,要用notifyAll,用原来的就不行,这个是比较通用的。
对于多个生产者和消费者。
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyALL.
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
Lock接口
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是
Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
成功的 lock
操作与成功的 Lock 操作具有同样的内存同步效应。
成功的 unlock
操作与成功的 Unlock 操作具有同样的内存同步效应