线程
概念:
1.进程
在windows操作系统中可以同时并行运行多个应用,每个应用可以独立运行,互不干扰。这样的操作系统称为多任务操作系统,而每一个任务称为一个单独的进程。每一个进程都会独享各自的存储空间,进程之间通信和交互比较困难。
2.线程
一个应用可以同执行多个操作,比如打开一个游戏应用,游戏可以播放音乐,可以展示游戏的画面,还可以响应玩家的操作。一个应用对应的是一个进程,一个进程可以包含多个线程,线程是进程中的一个处理单元。多个线程可以共享一个进程的存储空间,所以线程之间的通信就比较简单。
进程是可以独立运行的一个应用,而线程不可以独立运行,它只通运行在进程当中。
3.常见的多线程的应用:
下载软件,qq聊天工具,word文本编辑器、jvm ……
创建线程:
1.继承thread类,重写run方法
下面展示一些
public class MyThread extends Thread {
//重写方法
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("thread:"+i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
System.out.println("这是一个线程测试类");
//创建一个线程对象
MyThread mt=new MyThread();
//启动线程,让线程进行入到就绪的状态
mt.start();
for(int i=0;i<10;i++) {
System.out.println("main:"+i);
}
System.out.println("主方法运行结束");
}
}
线程的启动:通过调用start()方法来启动线程, 这个方法调用后,由jvm调用线程的run()方法来执行线程体。
2.定义一个类,实现Runnable接口,实现接口的run()方法
public class MyThread2 implements Runnable {
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("thread:"+i);
}
}
}
第二种创建线程的方式启动线程,需要先创建线程对象,把线程对象作为参数传给Thread的构造方法,再调用Thread对象的start()方法
public class Demo2 {
public static void main(String[] args) {
System.out.println("这是一个线程测试的应用");
//1.实例化一个自定义的线程对象
MyThread2 mt2=new MyThread2();
//2.实例化一个Thread对象
Thread thread=new Thread(mt2);
//3.启动线程
thread.start();
for(int i=0;i<10;i++) {
System.out.println("main:"+i);
}
System.out.println("主方法运行结束!");
}
继承Thread和实现Runnable这两种创建线程的方式的区别?
1.如果一个类通过继承Thread父类的方式变成一个线程类,该类就不能再去继承其它的父类了,因为java是单继承,它的可扩展性会变差。如果通过实现Runnable接口的方式创建线程,它还可以去继承其它的类,具有更好的可扩展性。
2.两种创建线程的方式,启动线程的代码不一样。
线程创建的第三种方式是定义一个类实现Callable接口,实现接口中的call()方法。
多线程并发访问的安全问题:
多线程并发访问同一个资源,多个线程并发访问是依靠互相抢夺资源得以运行的,此时会发生并发访问的安全性问题。
public class Ticket {
private int count;
public Ticket(int count) {
this.count=count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
public class Seller implements Runnable {
private Ticket ticket=null;
public Seller(Ticket ticket) {
this.ticket=ticket;
}
@Override
public void run() {
while(this.ticket.getCount()>0) {
//买出一张票
this.ticket.setCount(this.ticket.getCount()-1);
System.out.println("售票员"+Thread.currentThread().getId()+"买出一张票,剩余:"+this.ticket.getCount());
}
}
}
public class Demo3 {
public static void main(String[] args) {
Ticket ticket=new Ticket(100);
Seller s1=new Seller(ticket);
Seller s2=new Seller(ticket);
Seller s3=new Seller(ticket);
//启动一个售票的线程
new Thread(s1).start();
new Thread(s2).start();
new Thread(s3).start();
}
}
线程同步:
为了解决多线程并发访问共享资源时带来的安全性问题,可以为要访问的共享资源添加一个同步锁,一个线程先抢夺到这个共享资源,就可以优先使用这个资源,当使用完成之后,再释放这个锁,这时其它线程才有资格再去抢夺这个资源,这样,多线程在访问共享资源的时候相当于是交替运行,这样就不会出现数据安全性问题了。
public class Seller implements Runnable {
private Ticket ticket=null;
public Seller(Ticket ticket) {
this.ticket=ticket;
}
@Override
public void run() {
/*while(this.ticket.getCount()>0) {
//买出一张票
this.ticket.setCount(this.ticket.getCount()-1);
System.out.println("售票员"+Thread.currentThread().getId()+"买出一张票,剩余:"+this.ticket.getCount());
}*/
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同步代码块儿(添加同步锁)
synchronized(Ticket.class) {
if(this.ticket.getCount()<=0) {
return;
}
this.ticket.setCount(this.ticket.getCount()-1);
System.out.println("售票员"+Thread.currentThread().getId()+"买出一张票,剩余:"+this.ticket.getCount());
}
}
}
}
在进行线程同步时,所选择的同步锁对象可以是共享的资源,也可以是任意一个类的字节码(Integer.class)
同步应用的另一种场景:
参考:
package com.fc.thread;
public class Demo6{
public static void main(String[] args) {
Student s=new Student();
new Thread(new MyTh1(s)).start();
new Thread(new MyTh2(s)).start();
}
}
class Student {
String name="韩梅梅";
String gender="女";
}
class MyTh1 implements Runnable{
private Student s;
public MyTh1(Student s) {
this.s=s;
}
@Override
public void run() {
while(true) {
synchronized (Student.class) {
if (s.name.equals("韩梅梅")) {
s.name = "李雷";
s.gender = "男";
} else {
s.name = "韩梅梅";
s.gender = "女";
}
}
}
}
}
class MyTh2 implements Runnable{
private Student s=null;
public MyTh2(Student s) {
this.s=s;
}
@Override
public void run() {
while(true) {
synchronized (Student.class) {
System.out.println("姓名:" + s.name + "性别:" + s.gender);
}
}
}
}
实现线程同步的另一个案例:
定义一个学生类,封装学生的基本信息:姓名,性别
public class Student {
public String name="韩梅梅";//姓名
public String gender="女";//性别
public Student() {
}
public Student(String name,String gender) {
this.name=name;
this.gender=gender;
}
}
定义一个线程任务,实现对学生信息的修改
public class MyThread1 implements Runnable {
private Student student;
public MyThread1(Student s) {
this.student=s;
}
@Override
public void run() {
while(true) {
synchronized (Student.class) {
if(this.student.gender.equals("女")) {
this.student.name="李雷";
this.student.gender="男";
}else {
this.student.name="韩梅梅";
this.student.gender="女";
}
}
}
}
}
定义一个线程任务,实现对学生信息的显示
public class MyThread2 implements Runnable {
private Student student;
public MyThread2(Student s) {
this.student=s;
}
@Override
public void run() {
while(true) {
synchronized (Student.class) {
System.out.println("姓名:"+this.student.name+"性别:"+this.student.gender);
}
}
}
}
如果两个并发访问,对共享的资源不作任何的保护处理,则会发生线程并发访问的错误。
public class Test1 {
public static void main(String[] args) {
Student s=new Student();
//创建一个修改学生信息的线程
MyThread1 mth1=new MyThread1(s);
//创建一个读取学生信息的线程
MyThread2 mth2=new MyThread2(s);
new Thread(mth1).start();
new Thread(mth2).start();
}
}
线程死锁:
死锁:是在多线程运行的过程中,A线程在执行任务的时候如果任务后续执行的条件是获得B线程拥有的资源,而B线程又锁定了该资源。B线程在执行任务的时候如果后续执行的条件是要获得A线程拥有的资源,而A线程又锁定了该资源。A和B这个两线程陷入一个互相等待的一个胶着状态,从而形成死锁。
避免死锁出现的方案:
1.尽量减少共享资源的数目。
2.避免在同步中嵌套同步。
导致死锁的案例:
public class MyScanner {
public void scan() {
System.out.println(Thread.currentThread().getId()+"扫描仪在吱油吱油的扫描......");
}
}
public class Printer {
public void print() {
System.out.println(Thread.currentThread().getId()+"打印机在嘎吱嘎吱的打印......");
}
}
public class Test2 {
public static Printer p=new Printer();
public static MyScanner s=new MyScanner();
public static void main(String[] args) {
//创建一个并启一个线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (p) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.print();
//扫描
synchronized (s) {
s.scan();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.scan();
synchronized (p) {
p.print();
}
}
}
}).start();
}
}
线程的等待和唤醒机制:
线程的等待和唤醒机制,多线程并发执行过程中,谁先抢到资源(CPU),谁就会先锁定共享资源然后向后执行,如果其它线程想要有机会抢占资源的话需要调用共享资源的wait方法,让当前拥有资源的线程进入到等待池中进行等待,一旦进入等待池进行等待就会让出锁对象,如果想要再次抢占资源的话需要通过共享资源的notify方法来唤醒等待池中的线程,再次进入锁池中去进行锁的抢夺。notifyAll方法可通知等待池中所有在等待的线程进入锁池中去再次抢夺锁资源。
public class Student {
public String name="韩梅梅";//姓名
public String gender="女";//性别
public boolean flag=true;//等待唤醒标识
}
public class Ask implements Runnable {
private Student s;
public Ask(Student s) {
this.s=s;
}
@Override
public void run() {
synchronized (s) {
while(true) {
if(this.s.flag==true) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("姓名:"+this.s.name+"性别:"+this.s.gender+"提了一个问题");
this.s.flag=true;
this.s.notify();
}
}
}
}
public class Change implements Runnable {
private Student s;
public Change(Student s) {
this.s=s;
}
@Override
public void run() {
synchronized (s) {
while(true) {
if(s.flag==false) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//改变学生基本信息
if(this.s.gender.equals("女")) {
this.s.name="李雷";
this.s.gender="男";
}else {
this.s.name="韩梅梅";
this.s.gender="女";
}
this.s.flag=false;
this.s.notify();
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Student s=new Student();
Ask ask=new Ask(s);
Change change=new Change(s);
new Thread(ask).start();
new Thread(change).start();
}
}
wait()和notifyAll()
public class Student {
public String name="韩梅梅";//姓名
public String gender="女";//性别
public boolean flag=true;//等待唤醒标识
}
public class Ask implements Runnable {
private Student s;
public Ask(Student s) {
this.s=s;
}
@Override
public void run() {
synchronized (s) {
while(true) {
if(this.s.flag==true) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("姓名:"+this.s.name+"性别:"+this.s.gender+"提了一个问题");
this.s.flag=true;
this.s.notifyAll();
}
}
}
}
public class Change implements Runnable {
private Student s;
public Change(Student s) {
this.s=s;
}
@Override
public void run() {
synchronized (s) {
while(true) {
if(s.flag==false) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//改变学生基本信息
if(this.s.gender.equals("女")) {
this.s.name="李雷";
this.s.gender="男";
}else {
this.s.name="韩梅梅";
this.s.gender="女";
}
this.s.flag=false;
this.s.notifyAll();
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Student s=new Student();
Ask ask1=new Ask(s);
Ask ask2=new Ask(s);
Change change1=new Change(s);
Change change2=new Change(s);
new Thread(ask1).start();
new Thread(change1).start();
new Thread(ask2).start();
new Thread(change2).start();
}
}
线程的状态
sleep和wait的区别?
sleep是Thread类的静态方法,当线程在执行状态遇到sleep会由执行状态进入到阻塞状态,阻塞解除后会再次进入到就绪状态等待cpu的调度,继而再次被执行。sleep不会放弃锁资源。而wait是Object的类的方法,当线程执行过程中获得了同步锁,同时被通知wait,此时当前线程会放弃对锁的占有,进入到等待池中进行等待,直到被notify或notifyAll的时候会进行入到锁池中,然后再抢占锁资源后继续执行。