概述
一、进程与线程
个人所理解进程与线程的关系,如图:
进程是资源的拥有者,所以切换中系统要付出较大的时空开销,如图中A-->B所占用的时间片段。因此导致系统中的进程数和切换频率不宜过高,限制了并发程度的提高,而线程不属于资源被分配的单位,只是共享所属进程的资源,因此可以轻装上阵,线程间的切换开销要比进程少得多,由于资源是共享的所以进程间的通信也比进程间通信容易得多。
二、两种多线程的方式
1.继承Thread类复写run方法
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread1=new MyThread();
//thread1.setName("这里定义线程名");
thread1.start();//启动该线程 注:此时共有main和thread1两个线程
}
}
class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
System.out.println("这里写线程所要执行的代码");
System.out.println(Thread.currentThread().getName()); //Thread.currentThread() 获取当前线程对向
}
// public MyThread(String name){
// super.setName(name);初始化同时命名线程
// }
}
2.实现Runnable接口
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.setName("这里定义线程名");
thread.start();// 启动该线程 注:此时共有main和thread两个线程
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("这里写线程所要执行的代码");
}
}
三、两种方式的比较(以售票为例,多个线程同时对Ticket类的实例对象进行操作)
1.以继承Thread类方式
如果以这种方式实现多线程操作共享数据,需要将共享资源作为继承于Thread的类成员变量,并提供set方法或在构造方法中予以赋值,而每一个Thread的实例set进同一个资源的载体。如代码中:MyThread继承Thread类,多个MyThread的实例即多个线程操作Ticket的一个实例,则将Ticket类作为MyThread的成员
class Ticket {
private int num;//定义票数
public Ticket(int num) {
this.num = num;
}
/**
* 提供售票方法,每次执行票数减一
* @return
*/
public synchronized void sell() {
if (num > 0)
System.out.println(Thread.currentThread().getName() + "售出一张,剩余" + --num);
else
System.out.println("对不起,票已售完");
}
public int getNum() {
return num;
}
}
class MyThread extends Thread {
private Ticket ticket;//将要操作的资源类作为成员变量,并提供set方法,将实例对象set进来
public void setTicket(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (ticket) {
if (ticket.getNum() == 0)
break;
ticket.sell();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket(10000);
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.setTicket(ticket);
thread2.setTicket(ticket);
thread3.setTicket(ticket);// 这里三个新创建的并发线程都要set进同一个资源(同一个实例对象)
thread1.start();
thread2.start();
thread3.start();
}
}
2.以实现Runnable接口的方式(列举多种写法)
- 在实现Runnable接口的同时继承要操作的共享资源类,如代码中新建MyRunnable extend Ticket implements,但Ticket中票数num要设为protect
class Ticket {
protected int num;// 定义票数
public Ticket(int num) {
this.num = num;
}
/**
* 提供售票方法,每次执行票数减一
*
* @return
*/
public synchronized void sell() {
if (num > 0)
System.out.println(Thread.currentThread().getName() + "售出一张,剩余" + --num);
else
System.out.println("对不起,票已售完");
}
public int getNum() {
return num;
}
}
class MyRunnable extends Ticket implements Runnable{
public MyRunnable(int num) {
super(num);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized (this) {
if(num==0)break;
sell();
}
}
}
}
public class ThreadDemo{
public static void main(String[] args) {
Runnable myRunnable=new MyRunnable(10000);
Thread thread1=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable);
Thread thread3=new Thread(myRunnable);
thread1.start();
thread2.start();
thread3.start();
}
}
- 仅实现Runnable接口不继承资源类,而是将资源类作为成员变量set或构造函数中赋值
class Ticket {
privateint num;// 定义票数
public Ticket(int num) {
this.num = num;
}
/**
* 提供售票方法,每次执行票数减一
*
* @return
*/
public synchronized void sell() {
if (num > 0)
System.out.println(Thread.currentThread().getName() + "售出一张,剩余" + --num);
else
System.out.println("对不起,票已售完");
}
public int getNum() {
return num;
}
}
class MyRunnable implements Runnable{
private Ticket ticket;
public void setTicket(Ticket ticket) {
this.ticket = ticket;
}
public MyRunnable(Ticket ticket) {
// TODO Auto-generated constructor stub
this.ticket=ticket;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized (this) {
if(ticket.getNum()==0)break;
ticket.sell();
}
}
}
}
public class ThreadDemo{
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable(new Ticket(10000));
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
Thread thread3 = new Thread(myRunnable);
thread1.start();
thread2.start();
thread3.start();
}
}
- 直接用共享资源类去实现Runnable接口,这种方法还可以继续继承其他类
class Ticket implements Runnable {
privateint num;// 定义票数
public Ticket(int num) {
this.num = num;
}
/**
* 提供售票方法,每次执行票数减一
*
* @return
*/
public synchronized void sell() {
if (num > 0)
System.out.println(Thread.currentThread().getName() + "售出一张,剩余" + --num);
else
System.out.println("对不起,票已售完");
}
public int getNum() {
return num;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (this) {
if (num == 0) break;
sell();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Runnable ticket = new Ticket(10000);
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
Thread thread3 = new Thread(ticket);
thread1.start();
thread2.start();
thread3.start();
}
}
由此可见,实现Runnable这种方式除了可以避免java单继承的局限外,写法多变,更为灵活,可以根据实际情况采取相应的方法,在实际开发中也更为常用。
- 初始化状态:调用 new方法产生一个线程对象后、调用 start 方法前所处的状态。
- 就绪状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入就绪状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到就绪状态。多个线程间对cpu的争夺,都是以该状态为前提,即只有就绪状态的线程才有资格争取cpu的执行。
- 运行状态:线程调度程序从就绪线程池中选择一个线程执行后所处的状态。这也是线程进入运行状态的唯一一种方式。
- 等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态但非就绪。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
- 消亡状态:当线程的run()方法完成时的状态。
class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
System.out.println("Thread.sleep(这里是毫秒)");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.设置优先级
public class ThreadDemo {
public static void main(String[] args) throws Exception{
Runnable myrRunnable=new MyRunnable();
Thread thread1=new Thread(myrRunnable);
Thread thread2=new Thread(myrRunnable);
thread1.setPriority(7);//这里可设优先级1-10代表由低到高
thread1.start();
thread2.start();
}
}
static int MAX_PRIORITY 线程可以具有的最高优先级。
static int MIN_PRIORITY 线程可以具有的最低优先级。
static int NORM_PRIORITY 分配给线程的默认优先级。
3.线程让步yield()
之前提到所有可争夺cpu执行权的线程都应该是就绪状态,但是如果正在执行的线程一只占用cpu不主动释放,会导致就绪线程吃内的线程在一段时间内得不到执行,而该时间内总是由运行着的线程霸占。结果是cpu在一个线程上处理一大批数据后,才切换到另一个线程上去。而yield()方法则是让当前线程主动放权,即有运行状态主动转为就绪状态,这样线程切换的频率大大提高,效果是cpu每在一个线程上处理几条数据就切换到另一个线程。
public class JoinDemo{
public static void main(String[] args) throws Exception {
int[] nums = new int[6];
Add add = new Add(nums);
Thread thread1 = new Thread(add);
Thread thread2 = new Thread(add);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();//此时共main、thread1、thread2三个线程
thread1.join();//main读到这条与语句后,main只有在thread1结束后再运行(实际是main的线程栈追加到thread1的末尾)。此时只有thread1和thread2在运行
thread2.join();//main方法读这条语句时刻,运行的语句有main、thread2,读完该条语句后,只有thread2运行结束后才执行main
System.out.println(Arrays.toString(nums));//此时只有main线程
}
}
class Add implements Runnable {
private int[] nums;
@Override
public void run() {
// TODO Auto-generated method stub
try {
mainDo();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void mainDo() throws InterruptedException {
for (int i = 0; i < 3; i++) {
synchronized (this) {
for (int j = 0; j < nums.length; j++) {
Thread.sleep(new Random().nextInt(500));
if (nums[j] == 0) {
nums[j] = new Random().nextInt(999) + 1;
System.out.println(Thread.currentThread().getName() + "向num[" + j + "]添加" + nums[j]);
break;
}
}
}
}
}
public Add(int[] nums) {
this.nums = nums;
}
}
通过以上11行和12行的语句使得打印结果如下:
class Ticket {
private int num;//定义票数
public Ticket(int num) {
this.num = num;
}
/**
* 提供售票方法,每次执行票数减一
* @return
*/
public void sell() {
if (num > 0)
System.out.println(Thread.currentThread().getName() + "售出一张,剩余" + --num);
}
public int getNum() {
return num;
}
}
class MyThread extends Thread {
private Ticket ticket;//将要操作的资源类作为成员变量,并提供set方法,将实例对象set进来
public void setTicket(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
if (ticket.getNum() <= 0)
break;
ticket.sell();
Thread.yield();
}
}
}
运行结果:
1.同步代码块,代码块内的语句执行过程不会被中断
synchronized(obj)
{ //obj表示同步监视器,是同一个同步对象,该例中可以将共享资源ticket作为监视器,个人理解要保证同步的线程监视器对象必须是同一个
/**.....
TODO SOMETHING
*/
}
2.方法的同步,加入修饰符synchronized ,监视器为this,格式如下,
synchronized 返回值类型 方法名(参数列表){
/**.....
TODO SOMETHING
*/
}
注:由于在调用静态方法时,对象实例不一定被创建,因此,就不能使用this作为监视器,而是该静态方法所属类的字节码。
常见的延迟单例模式中监视器就为Single.class
class Single {
private Single() {
// TODO Auto-generated constructor stub
}
private static Single instance = null;
public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null)
instance = new Single();
}
}
return instance;
}
}
3.ReentrantLock代替
synchronized
class MyRunnable extends Ticket implements Runnable{
ReentrantLock lock=new ReentrantLock();
public MyRunnable(int num) {
super(num);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
lock.lock();
if(num==0)break;
sell();
lock.unlock();
}
}
}
七、关于死锁
关于汤子瀛版操作系统中描述的哲学家进餐问题,五位哲学家围坐在圆桌进餐,但每人仅右手边放置一只筷子,哲学家只有在同时获得两只筷子时才会进餐,进餐完毕释放两只筷子,不进餐时进行思考(等待),若有一位哲学家获得一只筷子,不会主动释放而是等待另一只筷子(请求保持),哲学家间不可抢夺筷子。如此循环,就有可能出现五个人手中各有一只筷子,在等待另一只筷子,造成再也无人进餐。这种情况就是死锁。
进程和线程发生死锁的必要条件:
对资源的互斥使用、不可强占、请求和保持、循环等待,当满足这四个条件时就有可能发生死锁,但是只要破坏其中任意一个条件就能避免思索。
Java多线程中出现死锁的例子(这里的资源指的是同步时的监视器对象,同步代码块使用监视器对象就是占有该资源):
class Resource {
public static Object object1 = new Object();
public static Object object2 = new Object();
public static Object object3 = new Object();
public static Object object4 = new Object();
}
class MyRunnable1 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (Resource.object1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Resource.object2) {
System.out.println("执行MyRunnable1.fun()");
}
}
}
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (Resource.object2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Resource.object1) {
System.out.println("执行MyRunnable2.fun()");
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Runnable runnable1 = new MyRunnable1();
Runnable runnable2 = new MyRunnable2();
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
关于避免死锁,个人理解:对于上述代码中出现了thread1和thread2互斥使用资源object1和object2,并且相互等待(循环等待)对方线程占有的资源(锁),同时同步代码块也满足了请求保持和不可抢占。解决办法是可以将object1和object2两个资源(锁)封装成一个对象(整体)(锁)。这样可以避免循环等待。对应哲学家进餐问题中,将两只筷子封装成一套餐具,只有得到一套餐具时才可以进餐,在多线程中也应尽量避免锁的嵌套使用。以免造成循环等待的情况出现。
八、线程通讯
wait():让当前线程放弃监视器进入等待,直到其他线程调用同一个监视器并调用notify()或notifyAll()为止。
notify():唤醒在同一对象监听器中调用wait方法的第一个线程。
notifyAll():唤醒在同一对象监听器中调用wait方法的所有线程。
下面的例子是通过两个线程向一个数组内添加元素,通过线程间的通讯达到一个线程添加一个元素后,另一个线程再添加一个元素如此往复的效果。
import java.util.Arrays;
import java.util.Random;
public class ThreadTest {
public static void main(String[] args) throws Exception {
int[] arrary = new int[1000];
Res res = new Res(arrary, false);
Runnable r1 = new add1(res);
Runnable r2 = new add2(res);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
//main线程最后打印结果
System.out.println(Arrays.toString(arrary));
}
}
/**
* @author Mr
* 资源类,封装有数组和线程间通讯的标志位
*/
class Res {
private int[] array;
private boolean sign;
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
public boolean isSign() {
return sign;
}
public void setSign(boolean sign) {
this.sign = sign;
}
public Res(int[] array, boolean sign) {
// TODO Auto-generated constructor stub
this.array = array;
this.sign = sign;
}
}
/**
* @author Mr
* 线程1负责向数组的前半部分添加元素
*/
class add1 implements Runnable {
private Res res;
public add1(Res res) {
// TODO Auto-generated constructor stub
this.res = res;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < res.getArray().length / 2; i++) {
synchronized (res) {
if (res.isSign())
try {
res.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int num = new Random().nextInt(99) + 1;
res.getArray()[i] = num;
System.out.println(Thread.currentThread().getName() + "向array["
+ i + "]添加" + num);
res.setSign(true);
res.notify();
}
}
}
}
/**
* @author Mr
* 线程2负责向数组的后半部分添加元素
*/
class add2 implements Runnable {
private Res res;
public add2(Res res) {
// TODO Auto-generated constructor stub
this.res = res;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = res.getArray().length / 2; i < res.getArray().length; i++) {
synchronized (res) {
if (!res.isSign())
try {
res.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int num = new Random().nextInt(99) + 1;
res.getArray()[i] = num;
System.out.println(Thread.currentThread().getName() + "向array["
+ i + "]添加" + num);
res.setSign(false);
res.notify();
}
}
}
}
效果如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest{
public static void main(String[] args) {
Res res = new Res("豆浆", 0);
Runnable r1 = new producer(res);
Runnable r2 = new customer(res);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);
Thread t3 = new Thread(r1);
Thread t5 = new Thread(r2);
Thread t6 = new Thread(r2);
t1.start();
t2.start();
t3.start();
t5.start();
t6.start();
}
}
class Res {
private String name;// 商品(资源)名
private int no;// 商品编号
private boolean sign;
private final Lock lock = new ReentrantLock();
private Condition condition_con = lock.newCondition();
private Condition condition_pro = lock.newCondition();
public Res(String name, int no) {
// TODO Auto-generated method stub
this.name = name;
this.no = no;
}
/**
* 生产
*/
public void pro() {
lock.lock();
while (sign) {
try {
condition_pro.await();// 生产相关线程wait
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产了" + name
+ ++no);
sign = true;
condition_con.signalAll();// 唤起消费相关线程
lock.unlock();
}
/**
* 消费
*/
public void cus() {
lock.lock();
while (!sign) {
try {
condition_con.await();// 消费相关线程wait
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out
.println(Thread.currentThread().getName() + "消费了" + name + no);
sign = false;
condition_pro.signalAll();// 唤起生产相关线程
lock.unlock();
}
}
/**
* @author Mr 生产者
*/
class producer implements Runnable {
private Res res;
public producer(Res res) {
// TODO Auto-generated constructor stub
this.res = res;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
res.pro();
}
}
}
/**
* @author Mr 消费者
*/
class customer implements Runnable {
private Res res;
public customer(Res res) {
// TODO Auto-generated constructor stub
this.res = res;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
res.cus();
}
}
}
最后
以上就是伶俐睫毛为你收集整理的JAVA 多线程的全部内容,希望文章能够帮你解决JAVA 多线程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复