淺談Java多線程實現(xiàn)及同步互斥通訊
Java多線程深入理解本文主要從三個方面了解和掌握多線程:
1. 多線程的實現(xiàn)方式,通過繼承Thread類和通過實現(xiàn)Runnable接口的方式以及異同點。
2. 多線程的同步與互斥中synchronized的使用方法。
3. 多線程的通訊中的notify(),notifyAll(),及wait(),的使用方法,以及簡單的生成者和消費者的代碼實現(xiàn)。
下面來具體的講解Java中的多線程:
一:多線程的實現(xiàn)方式
通過繼承Threa類來實現(xiàn)多線程主要分為以下三步:
第一步:繼承 Thread,實現(xiàn)Thread類中的run()方法。
第二步:定義一個Thread子類的實例。
第三步:通過調(diào)用Thread類的start()方法來啟動線程。
下面是簡單的代碼實現(xiàn):
class myThread extends Thread{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread mythread=new myThread();
mythread.setName("子線程");
mythread.start();
}
}
上面線程中用到的幾個方法:Thread.currentThraed().getName()方法得到當(dāng)前線程的名字。mythread.setName(“子線程”);為mythread線程重命名為“子線程”。
通過實現(xiàn)Runnable 接口來實現(xiàn)多線程主要分為以下幾步:
第一步:實現(xiàn)Runnable接口中的run()方法。生成一個Runnable的實例。
第二步:定義一個Thread類,并且將上面的Runnable實例傳給Thread類的構(gòu)造方法。
第三步:通過調(diào)用Thread類的start()方法來啟動線程。
下面是簡單的代碼實現(xiàn):
class myRunnable implements Runnable{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myRunnable myrunnable = new myRunnable();
Thread mythread=new Thread(myrunnable);
mythread.setName("子線程");
mythread.start();
}
}
既然通過繼承Thread類,和實現(xiàn)Runnable方法都能實現(xiàn)多線程的運行那么兩種方式到底有什么不同呢?下面通過一個買票的類子來看看到底二者有什么不同:
假設(shè)售票處總共有100張票,通過三個窗口來進(jìn)行售票也就是說我們要開辟三個不同的線程來實現(xiàn)買票:首先看看通過Thread類來實現(xiàn)買票的方式:
class myThread extends Thread{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread m1=new myThread();
myThread m2=new myThread();
myThread m3=new myThread();
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
結(jié)果太長了我不展示了,可以看到原本三個窗口共同買100張票的,但是結(jié)果每個窗口都買了100張票,這個很好理解因為每個窗口都是一個獨立的對象,都有自己的n=100。所以通過Thread類來實現(xiàn)買票功能是不可行的,其實用Thread方法來實現(xiàn)多線程,其中每個線程執(zhí)行的代碼并不是同一段代碼。
下面來看看實現(xiàn)Runnable接口是怎樣實現(xiàn)買票功能的:
class myRunnable implements Runnable{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myRunnable myrunnable=new myRunnable();
Thread m1=new Thread(myrunnable);
Thread m2=new Thread(myrunnable);
Thread m3=new Thread(myrunnable);
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
可以看出上面三個線程公用的是同一個Runnable的子類,所以只是開辟三條線程來執(zhí)行同一段runnable的代碼。所以不會出現(xiàn)買300張票的情況。但是這個程序還是有問題的當(dāng)講到了后面的線程的同步與互斥后我們再來完善這段程序。
二:多線程的同步與互斥中synchronized和volatile的使用方法。
現(xiàn)在截取一段上面代碼執(zhí)行過程中出現(xiàn)的問題并且分析一下問題是如何產(chǎn)生的,再來通過synchronied來解決此問題。
:窗口2…1
:窗口1…1
:窗口3…1
:窗口1…2
:窗口2…2
:窗口1…3
:窗口3…2
:窗口1…4
:窗口2…3
:窗口1…5
:窗口3…3
:窗口1…6
:窗口2…4
上面代碼的結(jié)果是通過實現(xiàn)Runnable接口產(chǎn)生的,上面窗口1,窗口2,窗口3,同時產(chǎn)生1是怎么產(chǎn)生的呢?
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
這是三個線程共同執(zhí)行的同一段代碼,上面結(jié)果產(chǎn)生的一種原因可能是當(dāng)窗口2執(zhí)行完輸出i=1時此時虛擬機(jī)執(zhí)行了窗口2,窗口2執(zhí)行輸出I,此時的i還未執(zhí)行++,所以i的值還是1,此時虛擬機(jī)又把執(zhí)行權(quán)給了窗口3,這個時候窗口3輸出的i仍然是1,程序產(chǎn)生上面問題的主要原因是存在公共變量i,i的值在程序執(zhí)行的過程中未保持同步。上面的for循環(huán)體應(yīng)該單獨執(zhí)行完之后才能讓其他的線程搶占虛擬機(jī)。Synchronized關(guān)鍵字就是用來實現(xiàn)保證線程在執(zhí)行一段公共資源是不被其他線程搶占。
被synchronized修飾的代碼塊稱作同步代碼塊,被synchronized修飾的方法稱為同步方法。下面通過加入synchronized關(guān)鍵字來實現(xiàn)買票功能:
class myRunnable implements Runnable {
int n = 100;
public void run() {
while (true) {
synchronized (this) {
if (n > 0) {
if (n % 10 == 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(":" + Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myRunnable myrunnable = new myRunnable();
Thread m1 = new Thread(myrunnable);
Thread m2 = new Thread(myrunnable);
Thread m3 = new Thread(myrunnable);
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
此時是可以正確的完成售票功能的。
上面代碼中synchronized(this)中的this代表的是當(dāng)前的對象,因為三個線程執(zhí)行的都是myRunnable 的對象,所以三個線程公用的是同一個鎖,其實這個this可以用任何的對象來代替,一般我們可以 String str=new String(“”);雖然str的值為空字符串,但是也是一個對象。Synchronized實現(xiàn)互斥的原理是每一個對象都有一個特定的變量值,當(dāng)任何一個線程調(diào)用了synchronized想要進(jìn)入公共資源區(qū)時,先判斷該變量的值,若該變量的值為0則可以進(jìn)入公共資源區(qū),進(jìn)程在進(jìn)入公共資源區(qū)之前先把對象的中的該變量值變?yōu)?,出同步區(qū)后再將該變量的值變?yōu)?,從而實現(xiàn)線程互斥訪問公共資源。
三:多線程的通訊中的notify(),notifyAll(),及wait(),的使用方法,以及簡單的生成者和消費者的代碼實現(xiàn)。
在講解notify(),notifyAll(),wait()之前,先看看生產(chǎn)者和消費者問題:生產(chǎn)者生產(chǎn)面包,消費者消費面包,但是存放面包的容器有限,生產(chǎn)者一次最多只能生產(chǎn)20個面包,消費者每次在容器中拿一個面包。通過分析可以知道,當(dāng)生產(chǎn)者生產(chǎn)了20個面包之后必須停下來,等容器里的面包數(shù)目小于20個時再繼續(xù)生產(chǎn),消費者看到容器里面面包個數(shù)為0時也必須停下來,等到有面包時才能消費。這時候就涉及到了生產(chǎn)者和消費者的通信。notify()是用于喚醒等待隊列中的線程,wait()用于阻塞當(dāng)前線程。Notify和wait()都必須用于synchronized修飾的同步代碼塊或同步方法中。
下面直接看生產(chǎn)者消費者代碼。
class Consumer implements Runnable {
Clerk clerk;
Consumer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
while(true)
clerk.consumeProduct();
}
}
class Producter implements Runnable {
Clerk clerk;
Producter(Clerk clerk)
{
this.clerk = clerk;
}
public void run() {
while(true)
clerk.addProduct();
}
}
class Clerk {
int product ;
public synchronized void consumeProduct() {
while (true) {
if (product <= 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
product--;
notifyAll();
System.out.println("消費者消費了:" + product);
}
}
}
public synchronized void addProduct() {
if (product > 20) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
product++;
notifyAll();
System.out.println("生產(chǎn)者生產(chǎn)了:" + product);
}
}
}
public class Test {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Consumer consumer=new Consumer(clerk);
Thread c=new Thread(consumer);
Producter producter=new Producter(clerk);
Thread p=new Thread(producter);
c.start();
p.start();
}
}
以上這篇淺談Java多線程實現(xiàn)及同步互斥通訊就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java基礎(chǔ)之三大控制流程結(jié)構(gòu)
這篇文章主要介紹了Java基礎(chǔ)之三大控制流程結(jié)構(gòu),文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
springCloud gateWay 統(tǒng)一鑒權(quán)的實現(xiàn)代碼
這篇文章主要介紹了springCloud gateWay 統(tǒng)一鑒權(quán)的實現(xiàn)代碼,統(tǒng)一鑒權(quán)包括鑒權(quán)邏輯和代碼實現(xiàn),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02
java基礎(chǔ)之?dāng)?shù)組常用操作總結(jié)(必看篇)
下面小編就為大家?guī)硪黄猨ava基礎(chǔ)之?dāng)?shù)組常用操作總結(jié)(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06
SpringBoot整合ELK實現(xiàn)日志監(jiān)控
這篇文章主要為大家詳細(xì)介紹了SpringBoot整合ELK實現(xiàn)日志監(jiān)控的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11
java實現(xiàn)拉鉤網(wǎng)上的FizzBuzzWhizz問題示例
這篇文章主要介紹了java實現(xiàn)拉鉤網(wǎng)上的FizzBuzzWhizz問題示例,需要的朋友可以參考下2014-05-05
淺談對象數(shù)組或list排序及Collections排序原理
下面小編就為大家?guī)硪黄獪\談對象數(shù)組或list排序及Collections排序原理。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09

