多线程
一、线程和进程
1)进程:是一个在执行中的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
2)线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。
3) 理解:一个进程中,至少有一个线程。在JVM 启动的时候会有一个进程叫java.exe该进程中,至少有一个线程,在负责 java 程序的执行,而且这个线程,运行的代码存在于 main 方法中,该线程称之为主线程。其实,JVM 中更细节的说明了虚拟机不止一个线程,包含了一个主线程和一个负责控制垃圾回收机制的线程。通过API的查找,java已经提供了对线程这类事物的描述,就是 Thread 类。
4)多线程的随机性:因为多个线程都获取 cpu 的执行权。 cpu 执行到谁,谁就运行。但是要明确的是:在某一时刻,只能有一个程序在运行。(多核除外。)CPU 在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行形容为在互相抢夺 CPU 的资源(执行权)我们可以形象的把多线程的运行形容为在互相抢夺 CPU 的资源(执行权)这就是多线程的特性,谁抢到谁执行。至于执行多长时间。CPU 说的算。
5)覆盖 run 方法的说明:Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run 方法。也就是说 Thread 类中 run 方法,用于存储线程需要运行的代码。所以说自定义线程,就是把代码放在run方法中。
6)线程都有自己的默认名称。Thread-编号,该编号从0开始。
currentThread:获取当前线程对象,这是一个静态的线程可以直接调用。
Ps:static Thread currentThread()
getName():获取线程的名称。
setName():设置线程名称,setName或者通过构造函数。
二、线程的生命周期
1)运行过程:
被创建: start()-->运行-- sleep(time) --> 冻结(:放弃执行资格)
运行-- sleep时间到 <-- 冻结(:放弃执行资格)
运行-- wait() --> 冻结(:放弃执行资格)
运行-- notify() <-- 冻结(:放弃执行资格)
2)消亡:stop() ,就是run方法中的结束。
3) 说明:没有资格执行的状态,冻结状态。当wait状态的时候,线程冻结了,没办法自动重启,这时候可以使用notify方法唤醒线程。
4)临时阻塞:具备运行资格,但是没有执行权。
线程的图解:
三、创建线程的两种方式
总结两种方式的区别:
继承 Thread :线程代码存放在 Thread 子类 的 run 方法中。
实现 Runnable:线程代码存放在接口的子类的 run 方法中。
方式一:继承 Thread 类。
步骤:
1、定义类继承 Thread 。
2、复写 Thread 类中的 run 方法。目的:将自定义的代码存储在 run 方法中,让线程运行。
3、调用线程的 start 方法。该方法有两个作用,启动线程,调用run方法。
简单的继承 Demo:
package study.part1.day009.thread;
public class ThreadDemo2 {
public static void main(String[] args) {
Test t1 = new Test("t1");
Test t2 = new Test("t2");
t1.start();
t2.start();
/**
直接调用 run 方法,没有线程的随机性。
t1.run();
t2.run();
*/
for (int i = 0; i < 60; i++) {
System.out.println("main----->"+i);
}
}
}
class Test extends Thread{
//private String name;
//不需要再自定义传入线程名称,因为父类已经干好了。所以写一个super就够了
Test(String name){
//this.name = name;
super(name);
}
/**
* 复写 run 方法
*/
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+this.getName()+"--Test Run:"+i);
}
}
}
方法二:实现 Runnable 接口。
步骤:
1、定义类实现 Runnable 接口。
2、覆盖 Runnable 接口中的 Run 方法。将线程要运行的方法存放在该 run 方法中。
3、通过 Thread 类建立线程对象。
4、将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
Ps:为什么要将 Runnable接口的子类对象传递给 Thread 的构造函数。因为,自定义的 run 方法所属的对象是Runnable 接口的子类对象,所以要让线程去执行指定对象的 run 方法。就必须明确该 run 方法所属的对象。
5、调用 Thread 的 start 方法开启线程,并调用 Runnable接口子类的 run 方法。
四、多线程的安全问题
java 对于多线程的安全问题,提供了专业的解决方式。就是同步代码块。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
1)最常用的线程使用方法:implements,就是用实现的方法写线程Thread。
2)运行时出现安全问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。另一个线程参与了进来执行,导致了共享
数据的错误。
3)解决方法:在对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其它线程不可以参与执行。java 对于多线程的安全问题,提供了专业的解决方式。就是同步代码块。
4)同步的前提:
1、必须要有两个或者两个以上的线程才需要synchroized锁上。
2、必须是多个线程同时使用一个锁。
5)实现方式:
synchronized(对象){
需要被同步的代码//同步带共享操作的数据,如if的判断tick开始。
}
对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程,即使获取了 CPU 的执行权,也进不去,因为没有获取锁。
6)同步代码块的 Demo:
package study.part1.day009.thread;
public class ThreadDemo4 {
public static void main(String[] args) {
Ticket2 t = new Ticket2();
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 Ticket2 implements Runnable{
private int tick = 100;
Object obj = new Object();
@Override
public void run(){
while(true){
//synchronized:监视器,锁:确定当前只有一个线程在执行。
/**
synchronized(obj){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---sale:"+tick--);
}
}*/
this.show();
}
}
public synchronized void show(){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---sale:"+tick--);
}
}
}
7)银行金库 Demo:
需求:银行有一个金库。有两个储户分别存300员,每次存一百。存3次。
目的:该程序是否有安全问题:有的
如何找问题:
1、明确那些代码是多线程运行的代码。
2、明确共享数据。
3、明确多线程代码中哪些语句是操作共享数据的。
同步函数用的是哪一个锁呢?函数需要被对象调用。那么函数都有一个属性对象引用。就是this
所以同步函数使用的锁是this 。
package study.part1.day009.thread;
public class ThreadDemo5 {
public static void main(String[] args) {
Cust c = new Cust();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
class Bank{
private int sum;
Object o = new Object();
public synchronized void add(int n){
//synchronized(o){//可以写到方法上:同步代码块,2同步函数
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sum = sum+n;
System.out.println("sum="+sum);
}
}
class Cust implements Runnable{
private Bank b = new Bank();
@Override
public void run(){
for (int i = 0; i < 3; i++) {
b.add(100);
}
}
}
8)线程的死锁,同步中嵌套同步
package study.part1.day009.thread;
public class ThreadDeadLockDemo {
public static void main(String[] args) {
TestDemo tdt = new TestDemo(true);
TestDemo tdf = new TestDemo(false);
Thread t1 = new Thread(tdt);
Thread t2 = new Thread(tdf);
t1.start();t2.start();
}
}
class TestDemo implements Runnable{
private boolean flag;
TestDemo(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {
System.out.println("if lockb");
}
}
}else{
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {
System.out.println("else locka");
}
}
}
}
}
class MyLock{
static Object locka = new Object();
static Object lockb = new Object();
}
class MyLock2{
static Object locka = new Object();
static Object lockb = new Object();
}
class TestDemo2 implements Runnable{
private boolean flag;
TestDemo2(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized(MyLock.locka){
System.out.println("locka");
synchronized(MyLock.lockb){
System.out.println("lockb");
}
}
}
else{
synchronized(MyLock.lockb){
System.out.println("lockb");
synchronized(MyLock.locka){
System.out.println("locak");
}
}
}
}
}
9)静态的同步方法
如果同步函数被静态修饰后,使用的是什么锁呢?
通过验证发现不是 this ,因为静态方法中也不可以定义 this 静态进内存时,内存中没有本类对象,但是一定有该类对象的字节码文件对象。
类名.class 该对象的类型是 class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。也就是类名.class
类名.class 在内存中是唯一的。
静态的同步代码Demo:
package study.part1.day009.thread;
public class ThreadDemo6 {
public static void main(String[] args) {
Ticket6 t6 = new Ticket6();
Thread t1 = new Thread(t6);
Thread t2 = new Thread(t6);
t1.start();
t2.start();
}
}
class Ticket6 implements Runnable{
private static int tick = 100;
boolean flag = true;
@Override
public void run() {
if(flag){
while(true){
synchronized (Ticket6.class) {
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----code:"+tick--);
}
}
}
}else{
while(true){
show();
}
}
}
public static synchronized void show(){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...show:"+tick--);
}
}
}
五、线程间的通信之等待唤醒机制
1)线程间的通信:其实就是多个线程在同时操作一个资源,但是操作的动作不同。
2)等待唤醒机制:其实就是 wait 方法和 notify 方法的使用。
使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
3)为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步线程时,都必须要标识他们所操作线程的锁。只有同一个锁上的被等待线程,可以被同一个锁 notify 唤醒,不可以对不同锁中的线程唤醒。也就是说,等待和唤醒必须是同一个锁。而锁,可以是任意对象,所以可以被任意对象调用的方法定义在 Object 中。
等待唤醒机制示例:
package study.part1.day010.threadtx;
public class ThreadTX2 {
public static void main(String[] args) {
Res1 r1 = new Res1();
new Thread(new Input1(r1)).start();
new Thread(new Output1(r1)).start();
}
}
class Res1{
private String name;
private String sex;
private boolean flag;
public synchronized void set(String name , String sex){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name+"---"+sex);
flag = true;
this.notify();
}
}
class Input1 implements Runnable{
private Res1 res;
Input1(Res1 res){
this.res = res;
}
@Override
public void run() {
int x = 0;
while(true){
if(x==0){
res.set("mike", "man");
}else{
res.set("raven", "woman");
}
x = (x+1)%2; //让x变成1;
}
}
}
class Output1 implements Runnable{
private Res1 res;
Output1(Res1 res){
this.res = res;
}
@Override
public void run() {
while(true){
res.out();
}
}
}
等到了运行的结果:
六、全部唤醒
对于多个生产者和消费者,就必须用 while + notifyAll(唤醒全部)。
1)为什么要用 while 判断标记呢?
原因:让被唤醒的线程再一次判断标记。
2)为什么定义 notifyAll ?
因为唤醒自己的同时还要唤醒对方的线程。只用 notify ,容易出现只唤醒本方线程的情况。导致程序中的所有线程都在等待。这也是比较有用的方法。
示例:
package study.part1.day010.threadtx;
public class ThreadTX3 {
public static void main(String[] args) {
Resource reso = new Resource();
new Thread(new Producer(reso)).start();
new Thread(new Producer(reso)).start();
new Thread(new Custmer(reso)).start();
new Thread(new Custmer(reso)).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) {
e.printStackTrace();
}
}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"--生产--"+this.name);
this.flag = true;
this.notifyAll(); //全唤醒
}
public synchronized void out(){
while(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--消费-----"+this.name);
this.flag = false;
this.notifyAll();
}
}
/**
* 生产者
*/
class Producer implements Runnable{
private Resource reso;
Producer(Resource reso){
this.reso = reso;
}
@Override
public void run() {
while(true){
reso.set("+商品+");
}
}
}
class Custmer implements Runnable{
private Resource reso ;
Custmer(Resource reso){
this.reso = reso;
}
@Override
public void run() {
while(true){
reso.out();
}
}
}
运行结果:
七、另一种锁 Lock
线程间的通信的另一种锁的方法。在 JDK 1.5 中提供的多线程的升级解决方案:
1)将同步的synchronized替换成实现Lock操作。
2)将Object中的wait,notify,notifyAll,替换成condition对象。该对象可以 Lock 锁进行获取。
3)一个lock上可以多个相关的condition
4) Demo:
package study.part1.day010.threadtx;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTX5 {
public static void main(String[] args) {
Resource3 reso = new Resource3();
System.out.println("xxx");
new Thread(new Producer3(reso)).start();
new Thread(new Producer3(reso)).start();
new Thread(new Custmer3(reso)).start();
new Thread(new Custmer3(reso)).start();
System.out.println("xx");
}
}
class Resource3{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_cus = lock.newCondition();
public void set(String name){
lock.lock();
while(flag){
try {
condition_pro.await();
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"--生产--"+this.name);
flag = true;
condition_cus.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock(); //解锁的动作一定要执行。
}
}
}
public void out(){
lock.lock();
try {
while(!flag){
condition_cus.await();
System.out.println(Thread.currentThread().getName()+"--消费-----"+this.name);
flag = false;
condition_pro.signal();
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
/**
* 生产者
*/
class Producer3 implements Runnable{
private Resource3 reso;
Producer3(Resource3 reso){
this.reso = reso;
}
@Override
public void run() {
while(true){
reso.set("+商品+");
}
}
}
class Custmer3 implements Runnable{
private Resource3 reso ;
Custmer3(Resource3 reso){
this.reso = reso;
}
@Override
public void run() {
while(true){
reso.out();
}
}
}
八、停止线程
1) JDK 1.5 后,停止线程的 stop 方法已经过时,那如何停止线程呢?
只有一种 run 方法结束。
2) 开启多线程运行,运行代码通常是循环结构。只要控制循环,就可以让 run 方法结束,也就是线程结束。
3)特殊情况:当线程处于冻结状态。就不会读取标记,那么线程就不会结束。
处理方法:强制中断线程:interrupt
当没有指定的方式让冻结的线程回复到运行的状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态来,实际上就是获取运行资格。这样就可以操作标记,让线程结束。
守护线程,setDaemon(boolean b);
4) 扩展知识之join()特点:
当 A 线程执行到了 B 线程的 join 方法时,A 线程就会等待,等 B 线程都执行完,A 才会执行。join 可以用来临时加入线程执行。
强制中断线程的 Demo:
package study.part1.day010.threadtx;
public class ThreadTX6 {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
/**
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//t1.setDaemon(true);
int num = 0;
while(true){
if(num++==60){
t1.interrupt();
t2.interrupt();
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"----"+num);
}
}
}
class StopThread implements Runnable{
private boolean flag = true; //falg 标记
@Override
public void run() {
while(flag){
System.out.println(Thread.currentThread().getName()+"---run");
Thread.yield();
}
}
public void changeFlag()
{
flag = false;
}
}
运行结果:
九、JDK1.5后的新特性:关于 StirngBuilder 的线程安全讲解。
StringBuffer 和 StringBuilder
1)StringBuffer:线程安全的可变字符序列 ,是字符串缓冲区,是一个容器。
1、长度是可变的。
2、可以操作多个数据类型。
3、最终通过toString()方法变成字符串。
2) CURD create update read delete
1、存储。
StringBuffer append();
StirngBuffer insert(index,数据):可以将数据插入到指定index位置。
2、删除。
StringBuffer delete(start,end):删除缓冲区的数据,包含start,不包含end。
deleteCharAt(index):删除指定位置的字符。
3、修改。
StringBuffer replace(start,end,str)
void setCharAt(index,char)
4、将缓冲区中的数据指定到。
3)StringBuilder:JDK 1.5之后出现。
4)StringBuffer 和 StringBuilder 的对比。
StirngBuffer: 线程同步的。线程安全。多线程使用
StringBuilder:线程不同步的。线程不安全。单线程使用
5) 升级 StringBuilder 因素:
1、提高效率。
2、简化书写。
3、提高安全性。
6) Demo:
package study.base.day010.ThreadTX;
public class StringBufferDemo1 {
public static void main(String[] args) {
SB01();
SB02();
SB03();
}
public static void SB01(){
StringBuffer sb = new StringBuffer();
sb.append("as");
sb.append(true);
sb.append('a');
sb.append(5);
sb.insert(2, "xxx");
sc(sb.toString());
}
/**
* delete
*/
public static void SB02(){
StringBuffer sb = new StringBuffer("adfsadf");
sb.delete(1, 3); //删除了‘df’
sc(sb.toString());
sb = new StringBuffer("abcdefg");
sb.deleteCharAt(3);
sc(sb.toString());
//清空缓冲区示例:
//sb.delete(0, sb.length());
}
public static void SB03(){
StringBuffer sb = new StringBuffer("ABCDEFG");
sb.replace(1, 3, "XXOO");
sc(sb.toString());
sc(sb.reverse().toString());
}
public static void sc(String str){
System.out.println(str);
}
}