什么是線程
線程是比進(jìn)程更小的運(yùn)行單位,它被包含在進(jìn)程之中,是進(jìn)程實(shí)際的運(yùn)行單位,一個(gè)線程是指進(jìn)程單一的控制流,一個(gè)進(jìn)程可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。(來源百度)
- 并發(fā):同一時(shí)間段內(nèi),多個(gè)任務(wù)都在執(zhí)行(單位時(shí)間內(nèi)不一定同時(shí)執(zhí)行)
-并行:單位時(shí)間內(nèi),多個(gè)任務(wù)同時(shí)執(zhí)行
Java中如何創(chuàng)建線程
Java中創(chuàng)建線程的方式有三種
- 通過implement Runable 接口
- 通過extents Thread 類
- 通過Callable和Future創(chuàng)建線程
1.implement Runnable方式
public class threadSafe{
public void static main(String[] args){
Thread thread=new Thread(new Runnable() { //第一種方式 直接new一個(gè)Runnable 實(shí)例
@Override
public void run() {
System.out.println("this is a thread");
}
});
thread.start();
mythread mythread=new mythread();
Thread thread2=new Thread(mythread);//通過創(chuàng)建mythread實(shí)例創(chuàng)建線程
}
}
class mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
a++;
System.out.println("running thread"+" a:"+a);
}
}
}
后兩者有興趣的同學(xué)可去菜鳥教程上學(xué)習(xí),在這就不寫了.
https://www.runoob.com/java/java-multithreading.html
什么是線程安全
一個(gè)類是線程安全的,是指被多個(gè)線程訪問時(shí),類可以持續(xù)進(jìn)行正確的行為.
當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí),如果我們不考慮線程在運(yùn)行環(huán)境下的交替執(zhí)行和調(diào)度,并且不需要額外的同步及在調(diào)用方代碼不必做其它的協(xié)調(diào),那么我們稱這個(gè)類是線程安全的(來自java并發(fā)編程實(shí)戰(zhàn))
多線程中,程序的執(zhí)行順序我們是不知道的,比如什么時(shí)候執(zhí)行這段代碼
讓我們看看實(shí)際代碼中的輸出
生產(chǎn)者
class product implements Runnable{
private int DEAFULT_CUSOTME_NUM=5;
private Thread thread;
private String ThreadName;
private goods goods;
/**
* none constructor
*/
product(){}
/**
*
* @param ThreadName
* Get threadName for this thread
*/
product(String ThreadName,goods goods){
this.ThreadName=ThreadName;
this.goods=goods;
}
public void start(){
if(thread==null){
thread =new Thread(this,ThreadName);
thread.start();
}
}
@Override
public void run() {
SimpleDateFormat formatter=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
for(int i=0;i<5;i++){
System.out.println("thread name :" + thread.getName() + "consume goods:"
+ DEAFULT_CUSOTME_NUM + "remain goods:" +
goods.consumeGoods(DEAFULT_CUSOTME_NUM) + " current time"
+ formatter.format(new Date()) + "\n i:" + i);
; //default product five goods;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消費(fèi)者
class custome implements Runnable{
private int DEAFULT_CUSOTME_NUM=5; //default good num
private Thread thread;
private String ThreadName;
private goods goods;
/**
* none constructor
*/
custome(){}
/**
*
* @param ThreadName
* Get threadName for this thread
*/
custome(String ThreadName,goods goods){
this.ThreadName=ThreadName;
this.goods=goods;
}
public void start(){
if(thread==null){
thread =new Thread(this,ThreadName);
thread.start();
}
}
@Override
public void run() {
SimpleDateFormat formatter=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
for(int i=0;i<5;i++){
System.out.println("thread name :"+thread.getName()
+" product goods:"+DEAFULT_CUSOTME_NUM+"remain goods:"+
goods.addGoods(DEAFULT_CUSOTME_NUM)+" current time"
+formatter.format(new Date())+"\n i:"+i); //default product five goods
}
}
main方法
public class threadSafe {
static int DEFAULT_GOODS_NUM=0;
public static void main(String[] args) {
goods goods=new goods(DEFAULT_GOODS_NUM);
custome custome2=new custome("product ",goods);
product product1=new product("consume ",goods);
custome custome1=new custome("product 1",goods);
product product2=new product("consume 2",goods);
custome2.start();
product1.start();
custome1.start();
product2.start();
}
}
我們來觀察一下輸出
由于我加了threadSleep,所以會(huì)按從0-5的順序執(zhí)行
雖然i:0的時(shí)候,輸出順序是0->10->5->5,結(jié)果是沒有錯(cuò)誤的,因?yàn)槲覀儫o法控制線程什么時(shí)候開始輸出,比如執(zhí)行操作的時(shí)候,線程一起執(zhí)行,實(shí)際上讀入變量順序是這樣的,produc->consumer 2-> consume->product 1
但是執(zhí)行 i:1的時(shí)候,出現(xiàn)了問題,product 和product 1同時(shí)輸出了5。說明這兩個(gè)線程發(fā)生了像上面一樣的圖的情況,在product和product 1 執(zhí)行前,先執(zhí)行了consume方法,此時(shí)變量為0,當(dāng)product和product1執(zhí)行時(shí),同時(shí)讀入變量值0,執(zhí)行addgood方法,得到了一樣的結(jié)果。
這就出現(xiàn)了線程不安全的情況
如果將它變?yōu)榫€程安全
1.將goodnum設(shè)置為volatile.
為什么設(shè)置volatile有效呢,volatile的作用到底是什么
在當(dāng)前java內(nèi)存模型下,線程可以把變量保存到本地內(nèi)存,而不是在主存中進(jìn)行讀寫,這就可能造成一個(gè)線程在主存中修改了變量的值,而另一個(gè)線程還在使用它在寄存器中變量值的拷貝,造成數(shù)據(jù)的不一致
使用volatile聲明變量,目的是告訴jvm,這個(gè)變量是不穩(wěn)定的,每次使用它都應(yīng)該去主存中讀寫
volatilel 聲明的變量除了保證可見性,還能防止指令重排
2.在修改goodnum的方法中加入 synchronized
synchronized可以保證修飾的代碼中,任何時(shí)刻只能有一個(gè)線程執(zhí)行。
class goods {
private volatile int num;
/**
* none constructor
*/
goods(){
}
/**
*
*/
goods(int num){
this.num=num;
}
public void setNum(int num){
this.num=num;
}
public synchronized int getNum(){ //該代碼塊為同步代碼塊
return this.num;
}
public synchronized int addGoods(int num){
return this.num+=num;
}
public int consumeGoods(int num){
return this.num-=num;
}
}