现在很多企业招聘JAVA方向的人才,都需要会多线程编程,可见其重要性。在很多应用中,多线程编程都占据很重要的部分,比如说,游戏,杀毒等。因此,我也用一篇技术博客专门介绍多线程编程。
先介绍进程:指正在进行中(执行)的程序,简单的说就是一个独立的应用程序。在windows里面,就是多进程的。
可以看见每一个.exe的程序都是一个进程。
线程:指进程中的一个独立的控制单元,线程在控制进程的执行。我个人理解,进程的运行,实际上是由多个线程同时运行的结果。即从微观上而言,应用程序的运行就是多个线程在同时执行的结果。宏观而言,就是一个进程在运行。线程是CPU调度资源的最小单位。
java VM 启动时候会有一个进程java.exe
该进程中至少一个线程负责java程序的执行
而且这个线程运行的代码存在于main方法中
该线程称之为主线程
扩展:其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程,故属于多线程
关于进程我们理解了就行。重点介绍多线程
一个线程从产生到消亡,可能会经历的几种状态
理解这五种状态是我们能够熟悉掌握多线程编程的基础。
在平时应用中我们创建一个线程的方式有两种方式:
方式一:继承Thread类
创建线程的第一种方式:继承Thread类
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法
3,调用线程的start方法
该方法的两个作用:启动线程,调用run方法
多个线程都获取cpu的执行权,实际上cpu随机分给一个线程一块随机的时间片
当线程用完时间片以后,就把cpu的使用权让出来给别的线程,总之,在某一时刻
对应单核计算机,有且只有一个线程执行,由于cpu的快速切换,我们感觉不到切换
的过程,我们可以形象的把多线程的运行行为在相互抢夺cpu的执行权
代码如下:
public class ThreadDemo extends Thread{
@Override
public void run() {//用于存储线程要执行的代码
System.out.println("Hello world!");
}
}
方式二:实现Runnable接口
步骤:
public class RunableDemo implements Runnable {
private int ticket=100;
Object ob=new Object();
@Override
public void run() {//用于存储线程要执行的代码
// TODO Auto-generated method stub
//执行的代码
}
}
但是,在实际应用中,我们通常会遇到多线程安全问题。多线程安全问题:当多条语句在操作同一线程共享数据是,一个线程对多条语句只执行了一部分,还没有执行完, 此时另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
java对于多线程的安全提供了专业的解决方式
就是同步代码块
synchronized(对象){
代码块
}
同步的前提:
* 1、必须要有两个或者两个以上的线程运行
* 2、必须是多个线程使用同一个锁
* 好处:解决了多线程的安全问题
* 弊端:多个线程需要判断锁,较为消耗资源
注意:
非静态同步函数的对象锁为this
public synchronized void show(){//同步函数锁
ticket++;
System.out.println(Thread.currentThread().getName()+"runtime..."+ticket--);
}
静态同步函数所使用的锁是该方法所在类的字节码文件对象,即类名.class,静态方法里的同步锁都是使用的是类的字节码对象
public static synchronized void show(){//静态同步函数锁
ticket++;
System.out.println(Thread.currentThread().getName()+"runtime..."+ticket--);
}
当我们创建一个对象时,通常会用到单例设计模式
饿汉式--不存在线程安全问题
public class Demo{
private static final Demo demo=new Demo();
private Demo(){
}
public static Demo getInstance(){
return demo;
}
}
单例设计模式--懒汉式:有延迟加载特性,导致存在多线程安全问题,所以,我们一般都会写为下面这种形式
public class Demo1{
private static Demo1 demo=null;
private Demo1(){
}
public static Demo1 getInstance(){
if(demo==null){
synchronized(Demo1.class)
{
if(demo==null)
demo= new Demo1();
}
}
return demo;
}
}
死锁:导致死锁的原因是,两个线程互相等待资源,导致两边都无法得到自己的资源,而使自己无法运行。
class Demo1{
static Object obj1=new Object();
static Object obj2=new Object();
}
class Demo2 implements Runnable{
boolean flag;
Demo2(boolean flag){
this.flag=flag;
}
@Override
public void run(){
if(flag){
while(true){
synchronized(Demo1.obj1){
System.out.println("1");
synchronized(Demo1.obj2){
System.out.println("2");
}
}
}
}
else{
while(true){
synchronized(Demo1.obj2){
System.out.println("2");
synchronized(Demo1.obj1){
System.out.println("1");
}
}
}
}
}
}
怎样将一些代码放入同步代码块里
步骤
* 1、明确哪些代码是多线程运行代码
* 2、明确共享数据
* 3、明确多线程运行代码中哪些语句是操作共享数据的
下面代码可以具体说明
两个线程之间相互通信
类
public class ThreadConnectionDemo {
String name;
String sex;
boolean flag;
}
写入数据
/*
* 线程间进行通信
*/
public class InputDemo implements Runnable{
private ThreadConnectionDemo demo;
InputDemo(ThreadConnectionDemo demo){
this.demo=demo;
}
@Override
public void run() {
// TODO Auto-generated method stub
int i=0;
while(true){
synchronized(demo){
try{Thread.sleep(500);}catch(Exception e){}
if(demo.flag)
try{demo.wait();//必须放在同步快里,设置线程到等待状态
}catch(Exception e){}
if(i==0){
demo.name="zhangsan";
demo.sex="man";
}else{
demo.name="李四";
demo.sex="女";
}
i=(i+1)%2;
demo.flag=true;
demo.notify();//必须放在同步快里面,唤醒线程
}
}
}
}
取出数据
public class OutputDemo implements Runnable {
private ThreadConnectionDemo demo;
OutputDemo(ThreadConnectionDemo demo){
this.demo=demo;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(demo){
if(!demo.flag)
try{demo.wait();}catch(Exception e){}
System.out.println(demo.name+"......"+demo.sex);
demo.flag=false;
demo.notify();
}
}
}
}
把同步代码放在同步函数里面
public class ResourceConDemo {
private String name;
private String sex;
private int count=0;
public boolean flag=false;
public synchronized void set(String name,String sex){
if(flag)//避免多个线程安全问题
try{this.wait();}catch(Exception e){}//wait()方法会抛出InterruptException异常
this.name=name+"...."+count++;
this.sex=sex;
System.out.println(Thread.currentThread().getName()+"放入商品..."+this.name+"...."+sex);
flag=true;
this.notify();
}
public synchronized void out(){
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"取出商品..."+name+"............"+sex);
this.notify();
flag=false;
}
}
多个生产者和多个消费者问题,注意,保证共享数据安全
public class ResourceConDemo {
private String name;
private String sex;
private int count=0;
public boolean flag=false;
public synchronized void set(String name,String sex){
while(flag)//避免多个线程安全问题,避免生产和消费不一致情况
try{this.wait();}catch(Exception e){}
this.name=name+"...."+count++;
this.sex=sex;
System.out.println(Thread.currentThread().getName()+"放入商品..."+this.name+"...."+sex);
flag=true;
this.notifyAll();//唤醒所有的线程
}
public synchronized void out(){
while(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"取出商品..."+name+"............"+sex);
this.notifyAll();
flag=false;
}
}
生产者
public class MoreResource implements Runnable{
private ResourceConDemo resource;
MoreResource(ResourceConDemo resource){
this.resource=resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try{
resource.set("商品", "苹果");
}catch(Exception e){}
}
}
}
消费者
public class MoreConsumers implements Runnable {
private ResourceConDemo resource;
MoreConsumers(ResourceConDemo resource){
this.resource=resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try{
resource.out();
}catch(Exception e){}
}
}
}
主函数
public class RCDemo {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
ResourceConDemo t=new ResourceConDemo();
new Thread(new MoreResource(t)).start();//两个生产者
new Thread(new MoreResource(t)).start();
new Thread(new MoreConsumers(t)).start();//两个消费者
new Thread(new MoreConsumers(t)).start();
}
}
JDK1.5新特性,引入Lock锁
jdk1.5 新特性 Lock锁 他替换了synchronized wait notify等关键字
import java.util.concurrent.locks.*;
public class ResourceConDemo2 {
private String name;
private String sex;
private int count=0;
public boolean flag=false;
private Lock lock=new ReentrantLock();
private Condition condition_re=lock.newCondition();//一个lock锁可以由多个条件
private Condition condition_con=lock.newCondition();
public void set(String name,String sex)throws InterruptedException{
try{
lock.lock();
while(flag)
condition_re.await();
this.name=name+"...."+count++;
this.sex=sex;
System.out.println(Thread.currentThread().getName()+"放入商品..."+this.name+"...."+sex);
flag=true;
condition_con.signal();//唤醒消费者的线程
}
finally{
lock.unlock();//释放锁
}
}
public void out()throws InterruptedException{
try{
lock.lock();
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"取出商品..."+name+"............"+sex);
condition_re.signal();//唤醒生产者的锁
flag=false;
}finally{
lock.unlock();//释放锁
}
}
}
注意:
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒
不可以对不同锁中的线程进行唤醒
也就是说,等待和唤醒必须是同一个锁
调用wait()方法,导致当前线程等待,并放入到线程池里等待被唤醒,唤醒之后继续执行以前未执行的代码
Interrupted()强制把冻结状态的线程恢复到运行状态来,但是会抛出InterruptException异常
join方法:当A线程执行到了B线程的.join()方法是,A就会等待,等待B线程都执行完,A才会执行
作用:可以用来加入线程的执行
此外线程默认优先级为5
必须要明白的一点:CPU执行的任意时刻有且只有一个线程才拥有CPU的使用权,也就是说,任意时刻只有一个线程在运行,之所有多线程同时执行,是因为CPU切换的速度太快,导致我们无法感觉到。
总结:多线程编程,必须理解和掌握它的五种基本状态并且多联系,多分析,练多了就掌握了。