多线程的实现及其安全问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程的实现及其安全问题相关的知识,希望对你有一定的参考价值。

一、进程和线程概述

1、进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动,简单来说开启一个程序就开启了一个进程;

如果开启多个进程,它们之间是由于CPU的时间片在相互的切换;

2、线程:开启一个进程的一个任务,对于多线程:每一个线程都在争夺CPU的执行权(CPU的执行权具有随机性);

如果一个程序的执行路径有多条,那么该线程是多线程;反之,就单线程线程;线程是依赖于进程存在的!

3、Jvm是多线程 —— 至少开启了两条线程

main方法 主线程

gc() 垃圾回收线程

二、多线程的实现方式

1、继承Thread类

1)自定义一个类,该类继承Thread类

2)重写的run()方法

public class MyThread extends Thread{

public void run(){

耗时操作;

}

}

3)在主线程中创建该类对象

MyThread mt = new MyThread(); //创建几个对象,就有几个线程,去执行run()方法中的耗时操作

4)start()方法,启动线程

mt.start();

2、实现Runnable接口

1)自定义一个类,该类实现Runnable接口 //数据共享

2)重写的run()方法

public class MyRunnable implements Runnable{

public void run(){

耗时操作

}

}

3)在主线程中创建该类的对象

MyRunnable mr = new MyRunnable();

4)创建Thread类对象,将第三步的对象作为参数进行传递

Thread t1 = new Thread(mr);

Thread t2 = new Thread(mr,"名称"); //可以设置线程名称

5)start()方法,启动线程

t1.start();

t2.start();

3、使用线程池(主要用于计算)

1)自定义一个类,实现Callable接口

public class MyCallable implements Callable{}

2)实现里面的call方法

相当于run()方法,里面放一些耗时操作;

3)主线程中创建线程对象

ExecutorService threadPool = Executors.newFixedThreadPool(2) ; 参数表示线程的个数

4)用线程池对象提交任务

threadPool.submit(new MyCallable());

threadPool.submit(new MyCallable());

5)提交后结束线程池

threadPool.shutdown();

三、Thread的常用方法(Thread实现Runnable接口)

public static Thread currentThread()返回对当前正在执行的线程对象的引用

public final String getName()返回该线程的名称

public final void setName(String name)改变线程名称

public final void setDaemon(boolean on)当参数为true时,设置该线程为守护线程,当正在运行的线程都是守护线程时Java虚拟机退出

//该方法必须在启动线程前调用

public final void join()throws InterruptedException等待该线程终止 //等待该线程执行完毕,其他线程才能执行

public static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

public final void stop()强制停止执行 //已过时

public void interrupt()中断线程 //中断当前线程的这种状态(打破了一种状态)

public static void yield()暂停当前正在执行的线程对象,并执行其他线程

//线程的执行具有随机性,下次有可能又是它抢占到了CPU执行权,还是它接着执行

四、多线程的安全问题

egg:共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票

public class SellTicket implements Runnable{

private int ticket = 100;

@Override

public void run() {

while(true){

if(ticket > 0){

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ":" + ticket);

ticket --;

}else{

break;

}

}

}

}

public class SellTicketDemo {

public static void main(String[] args) {

SellTicket st = new SellTicket();

Thread t1 = new Thread(st,"窗口1");

Thread t2 = new Thread(st,"窗口2");

Thread t3 = new Thread(st,"窗口3");

t1.start();

t2.start();

t3.start();

}

}

1、问题:

1)出现了同一张票被卖了多次

由于CPU的执行具有原子性(最简单的操作)操作

2)出现了0票和负票

由于cpu的执行权具有随机性和延迟性导致

2、线程安全的检测条件

1)看我们当前的环境是否属于多线程程序

2)当前的多线程程序中是否有共享数据

3)是否多条语句对共性数据进行操作

3、多线程的同步机制

为了解决上述安全问题java提供了一个同步机制 —— 关键词sychronized

1)同步代码块的使用:

sychronized(任意对象){ 

  多条语句对共享数据进行操作的代码;

  }

2)注意事项:

A:所有线程只能使用同一个对象(将任意对象当作一个锁:使用的是同一把锁),所以不能使用匿名对象当做参数

B:注意同步代码块所包的代码不能将循环包进去,否则就不是多线程了

3)同步方法:

public static synchronized void 方法名(){

多条语句对共享数据进行操作的代码;

} //同步方法使用锁对象是this

静态的同步方法的锁对象是:类名.class(java中的反射机制)

4)Lock接口:

实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作

创建具体的锁对象

private Lock lock = new ReentrantLock(); //Lock接口不能实例化,提供了子实现类:ReentrantLock

public void lock()获取锁

多条语句对共享数据进行操作的代码;

public void unlock():释放锁

使用:一般使用try-finally将整个内容包起来,在finally中释放锁

4、修改后代码:

public class SellTicket implements Runnable{

private static int ticket = 100;

@Override

public void run() {

while(true){

sellTicket();

if(ticket <= 0){

break;

}

}

}

public static synchronized void sellTicket(){

if(ticket > 0){

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ":" + ticket);

ticket --;

}

}

}

public class SellTicketDemo {

public static void main(String[] args) {

SellTicket st = new SellTicket();

Thread t1 = new Thread(st,"窗口1");

Thread t2 = new Thread(st,"窗口2");

Thread t3 = new Thread(st,"窗口3");

t1.start();

t2.start();

t3.start();

}

}

5、和线程安全相关的类

StringBuffer sb = new StringBuffer();

Vector<T> v = new Vector<T>() ;

Hashtable<K, V> hs = new Hashtable<K,V>() ;

public static <T> List<T> synchronizedList(List<T> list)返回指定列表支持的同步(线程安全的)列表

List<T> list = Collections.synchronizedList(new ArrayList<T>());

五、死锁的问题

1、线程安全的弊端:

1)执行效率低

2)容易产生死锁

两个或两个以上的线程,抢占CPU的执行权,然后出现了互相等待的情况;

2、生产者消费者模式

分别进行产生数据和消费数据两条线程,针对同一资源进行操作

egg:

public class StudentDemo {

public static void main(String[] args) {

Student s = new Student();

SetThread st = new SetThread(s);

GetThread gt = new GetThread(s);

Thread t1 = new Thread(st);

Thread t2 = new Thread(gt);

t1.start();

t2.start();

}

}

public class Student {

private String name;

private int age;

public Student() {

super();

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

}

public class SetThread implements Runnable{

private Student s;

public SetThread(Student s) {

super();

this.s = s;

}

@Override

public void run() {

int i = 0;

while(true){

if(i % 2 == 0){

s.setName("张三");

s.setAge(30);

}else{

s.setName("李四");

s.setAge(40);

}

i ++;

}

}

}

/*

* 理想状态:

* 张三=30

* 李四=40

* 张三=30

* 李四=40

* 循环...

*/

public class GetThread implements Runnable{

private Student s;

public GetThread(Student s) {

this.s = s;

}

@Override

public void run() {

while(true){

System.out.println(s.getName() + "=" + s.getAge());

}

}

}

3、问题:

1)出现了 张三=40 线程随机性导致

解决方法:给代码加上同步代码块

2)并没有和理想状态一样,张三、李四依次出现,而是同一个数据被打印了多次

解决方法:等待唤醒机制

注意:同步锁,不同的线程之间使用的是同一把锁对象!

Object类中有关多线程的方法:

public final void wait()throws InterruptedException当前线程等待

public final void notify()唤醒在此对象监视器上等待的单个线程

public final void notifyAll()唤醒在此对象监视器上等待的所有线程

//这些方法一般是使用的锁对象进行调用,锁对象可以是任意对象,所以这几个有关多线程的方法在Object类中

4、修改后代码:

public class StudentDemo {

public static void main(String[] args) {

Student s = new Student();

SetThread st = new SetThread(s);

GetThread gt = new GetThread(s);

new Thread(st).start();

new Thread(gt).start();

}

}

public class Student {

private String name;

private int age;

private boolean falg;

public Student() {

super();

}

public synchronized void set(String name,int age){

if(falg == true){

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

this.name = name;

this.age = age;

falg = true;

this.notify();

}

public synchronized void get(){

if(falg == false){

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(this.name + "=" + this.age);

falg = false;

this.notify();

}

}

public class SetThread implements Runnable{

private Student s;


public SetThread(Student s) {

super();

this.s = s;

}

@Override

public void run() {

int i = 0;

while(true){

if(i % 2 == 0){

s.set("张三", 30);

}else{

s.set("李四", 40);

}

i++;

}

}

}

public class GetThread implements Runnable{

private Student s;

public GetThread(Student s) {

super();

this.s = s;

}

@Override

public void run() {

while(true){

s.get();

}

};

}

六、定时机制

1、定时器:可以进行任务的重复操作

可安排任务定时执行一次,或者定期重复执行

2、Timer类

1)Timer的使用

构造方法:public Timer()创建一个新计时器

方法: public void schedule(TimerTask task, Date time)安排在指定的时间执行指定的任务

参数1:task - 所要安排的任务

参数2:time - 执行该任务的时间毫秒值

public void schedule(TimerTask task,  Date firstTime,long period)每隔多少毫秒进行重复性的任务操作

public boolean cancel()取消此计时器任务

2)TimerTask类

需要自定义类去继承,并重写其run()方法;作为所要安排的任务

3)执行一个定时器,3秒之后爆炸,并且每隔2秒继续执行

public class TimerDemo{

public static void main(String[] args){

Timer t = new Timer();

t.schedule(new MyTask(), 3000, 2000);

}

}

class MyTask extends TimerTask{

@Override

public void run() {

System.out.println("bom,爆炸了");

}

}


以上是关于多线程的实现及其安全问题的主要内容,如果未能解决你的问题,请参考以下文章

Java中volatile关键字及其作用是什么?

27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)

27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)

多线程单连接中的 PDO::lastInsertId() 是不是安全?

java 22 - 12 多线程之解决线程安全问题的实现方式1

JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;