1.什么是线程和进程?两者之间的关系?
进程是程序的一次动态执行过程,线程和进程都是实现并发的基本单位。线程是比进程更小的执行单位,一个线程可以由多个线程组成,线程消亡了,进程还在,但进程消亡了,线程一定也随之消亡。
多线程的好处:在于可以提高CPU的利用率——任何一个程序员都不希望自己的程序很多时候没事可干,在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
然而我们也必须认识到线程本身可能影响系统性能的不利方面,以正确使用线程:
- 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多
- 多线程需要协调和管理,所以需要CPU时间跟踪线程
- 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题
- 线程太多会导致控制太复杂,最终可能造成很多Bug
继承Thread创建线程:
class Demo extends Thread
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run----"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//for(int x=0; x<4000; x++)
//System.out.println("Hello World!");
Demo d = new Demo();//创建好一个线程。
d.start();//开启线程并执行该线程的run方法。
//d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
for(int x=0; x<60; x++)//主线程
System.out.println("Hello World!--"+x+Thread.currentThread().getName());
/*
java VM 启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行
而且这个线程运行的代码存在于main方法中,该线程称之为主线程。
其实jvm启动不止一个线程,还有负责垃圾回收机制的线程。
*/
}
}
class Test extends Thread
{
//private String name;
Test(String name)
{
//this.name = name;
super(name);
}
public void run()
{
for(int x=0; x<60; x++)
{ //设置线程名称:setName()或者构造函数。
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
System.out.println(this.toString());
}
}
}
//创建两个线程,和主线程交替运行。
class ThreadTest
{
public static void main(String[] args)
{
Test t1 = new Test("one---");
Test t2 = new Test("two+++");
t1.start();
t2.start();
for(int x=0; x<60; x++)
{
System.out.println("main....."+x);
}
}
}
3.线程运行的五种状态 a.创建--继承Thread类和实现Runnable接口
b.就绪状态--调用start()方法后,线程有了执行的资格,等待获取cpu使用权
c.运行状态--线程有执行的资格且获得了cpu使用权
d.阻塞状态--I/O操作 wait() sleep()等导致阻塞,当阻塞条件解除重新回到就绪状态进行排队获取cpu执行权
e.死亡状态--stop()或者线程执行完毕
4.线程安全
class Ticket implements Runnable
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
try
{
Thread.sleep(10);//让当前正在执行的线程休眠,为了观察发现问题
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();//创建Runnable接口子类对象
Thread t1 = new Thread(t);//创建了一个线程;
Thread t2 = new Thread(t);//创建了一个线程;
Thread t3 = new Thread(t);//创建了一个线程;
Thread t4 = new Thread(t);//创建了一个线程;
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行代码,发现打印出0,-1,-2等错票。为什么会这样?
问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式--同步代码块(相对同步方法更加灵活)。
synchronized(对象)
{
需要被同步的代码
}
对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
将以上代码进行如下修改:
class Ticket implements Runnable
{
private int tick = 100;
Object obj = new Object();
public void run()
{
while(true)
{ //obj是对象锁,任何对象都可以是锁,这里可以用this、Ticket.class、TicketDemo2.class
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
else
break;
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
synchronized同步方法
/*银行一个小金库,由两个客户分别存3次钱,每次存100元,统计每次客户存钱后金库总额*/
public class BankDemo
{
public static void main(String[] args)
{
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
/*
//使用同步代码块的方法保障线程安全
class Bank
{
private int sum=0;
public void add(int n)
{
synchronized(this)
{
sum=sum+n;
try
{
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println("sum="+sum);
}
}
}
*/
//使用同步方法保障线程安全
class Bank
{
private int sum=0;
public synchronized void add(int n)
{
sum=sum+n;
try
{
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println("sum="+sum);
}
}
class Cus implements Runnable
{
private Bank b=new Bank();
public void run()
{ for(int i=0;i<3;i++)
{
b.add(100); // 如果在此处进行同步的话,就是要等一个客户执行自己的全部操作次数才让另外一个客户进入操作,不符合实际
}
}
}
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this,所以同步函数使用的锁是this。
/*
同步函数用的是哪一个锁呢?
函数都要被对象调用,那么函数都有一个所属对象给引用,就是this
验证同步函数的锁就是this
*/
public class ThisLockDemo1
{
public static void main(String[] args)
{
MyThread my=new MyThread();
Thread t1=new Thread(my);
Thread t2=new Thread(my);
t1.start();
try{Thread.sleep(100); }catch(Exception e){ e.printStackTrace();}
my.flag=false;
t2.start();
}
}
class MyThread implements Runnable
{
private int tic=30;
boolean flag=true;
public void run()
{
//Object obj=new Object();
if(flag)
{
while(true)
{
synchronized(this) //此处如果传入的参数是obj.那么输出数据将会错乱,因为多个线程间用的不是一个锁
{
if(tic>0)
{
try{Thread.sleep(100); }catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" sale @@@@@@:"+tic--);
}
}
}
}
else
{
while(true)
{
this.take();
}
}
}
public synchronized void take()
{
if(tic>0)
{
try{Thread.sleep(200); }
catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" take ######:"+tic--);
}
}
}
那么在静态的同步方法,对象锁又是什么情况?
/*
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。
静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class
*/
class Ticket implements Runnable
{
private static int tick = 100;
//Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket.class)//此处如果用this对象锁,卖票数据将会出错
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....@@同步代码块@ : "+ tick--);
}
}
}
}
else
while(true)
{
show();
}
}
public static synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....##同步方法#.... : "+ tick--);
}
}
}
class StaticMethodDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
5 死锁
//1.死锁是程序都在等待对方先完成,在僵持不下的情况下,就会使程序的执行处于停滞状态
//2.同步的嵌套会产生死锁,过多的同步会产生死锁
public class DeadLock
{
public static void main(String[] args)
{
MyLock lock1=new MyLock();
MyLock lock2=new MyLock();
lock1.flag=true;
lock2.flag=false;
Thread t1=new Thread(lock1);
Thread t2=new Thread(lock2);
t1.start();
t2.start();
}
}
class ZhangSan
{
public void say()
{
System.out.println("张三说你把画给我!");
}
public void get()
{
System.out.println("张三得到了画");
}
}
class LiSi
{
public void say()
{
System.out.println("李四说你把书给我!");
}
public void get()
{
System.out.println("李四得到了书");
}
}
class MyLock implements Runnable
{
boolean flag;
private static ZhangSan zs=new ZhangSan();//声明static类型对象,数据共享
private static LiSi ls=new LiSi();//static很重要
public void run()
{
if(flag)
{
zs.say();
synchronized(zs)
{
try{Thread.sleep(200);}catch(Exception e){}
synchronized(ls)
{
zs.get();
}
}
}else
{
ls.say();
synchronized(ls)
{
try{Thread.sleep(200);}catch(Exception e){}
synchronized(zs)
{
ls.get();
}
}
}
}
}
6 单例模式
详见:http://blog.csdn.net/a859522265/article/details/7176813
7.线程通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
请看生产者消费者代码示例:
思考:1.为什么生产者和消费者中要使用while循环判断? 使用while循环可以让被唤醒的线程每次都判断标记,保障数据的准确性。
2.为什么使用了notifyAll? 因为需要唤醒对方线程(如生产者唤醒消费者),使用notify的话会出现只唤醒本方线程(如消费者唤醒消费者),导致程序中的线程都处于等待状态。
/*线程同步---生产一部电脑消费一部电脑*/
public class ComputerDemo
{
public static void main(String args[])
{
Computer com=new Computer();
Producer pro=new Producer(com);
Consumer con=new Consumer(com);
Thread t1=new Thread(pro);
Thread t2=new Thread(pro);
t1.setName("生产线程A");
t2.setName("生产线程B");
Thread t3=new Thread(con);
Thread t4=new Thread(con);
t3.setName("@@@@@消费线程C");
t4.setName("@@@@@消费线程D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Computer
{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name)//生产电脑
{
while(flag)
{
try
{
this.wait();
}
catch (Exception e)
{
}
}
this.name=name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"生产"+this.name);
flag=true;
notifyAll();
}
public synchronized void out()//消费电脑
{
while(!flag)
{
try
{
this.wait();
}
catch (Exception e)
{
}
}
System.out.println(Thread.currentThread().getName()+"消费"+this.name);
flag=false;
notifyAll();
}
}
class Producer implements Runnable
{
Computer com;
public Producer(Computer com)
{
this.com=com;
}
public void run()
{
while(true)
{
com.set("电脑~~");
}
}
}
class Consumer implements Runnable
{
Computer com;
public Consumer(Computer com)
{
this.com=com;
}
public void run()
{
while(true)
{
com.out();
}
}
}
JDK5.0新特性之LOCK
import java.util.concurrent.locks.*;
public class ProducerComsumerDemo
{
/**
* JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作;
将Object中的wait,notify notifyAll,
替换成Condition对象的await();signal();signalAll();
该示例中,实现了本方只唤醒对方操作。
*/
public static void main(String[] args)
{
Res r = new Res();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Res
{
private String name;//商品名称
private int count=0;//商品编号
private boolean flag=false;//判断标识
private Lock lock=new ReentrantLock();
Condition con=lock.newCondition();
Condition pro=lock.newCondition();
public void set(String name) throws InterruptedException
{
lock.lock();//获取锁
try{
while(flag)
{
pro.await();//等待
}
this.name=name+count++;
System.out.println(Thread.currentThread().getName()+"生产者生产*****"+this.name);
flag=true;//修改标识
con.signal();//只唤醒对方线程
}
finally
{
lock.unlock();//释放锁
}
}
public void out() throws InterruptedException
{
lock.lock();//获取锁
try
{
while(!flag)
{
con.await();//等待
}
System.out.println(Thread.currentThread().getName()+"消费者消费@@@@@@@@@@"+name);
flag=false;
pro.signal();//唤醒对方线程
}
finally
{
lock.unlock();//释放锁
}
}
}
class Producer implements Runnable
{
Res r;
public Producer(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
try
{
r.set("商品");
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable
{
Res r;
public Consumer(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
try
{
r.out();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
8、停止线程
1.定义循环结束标记,因为线程运行代码一般都是循环,只要控制了循环即可。
class ThreadStopDemo
{
public static void main(String[] args)
{
ThreadStop ts=new ThreadStop();
Thread t1=new Thread(ts);
Thread t2=new Thread(ts);
t1.start();
t2.start();
int num=0;
while(true)
{
if(num++==50)
{
ts.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"main...run"+num);
}
System.out.println("over");
}
}
class ThreadStop implements Runnable
{
private boolean flag=true;
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"ThreadStop......run");
}
}
public void changeFlag()
{
flag=false;
}
}
2.使用interrupt(中断)方法。该方法是结束线程的冻结状态,使线程回到运行状态中来。
class ThreadStopDemo1
{
public static void main(String[] args)
{
ThreadStop ts=new ThreadStop();
Thread t1=new Thread(ts);
Thread t2=new Thread(ts);
t1.start();
t2.start();
int num=0;
while(true)
{
if(num++==50)
{
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"main...run"+num);
}
System.out.println("over");
}
}
class ThreadStop implements Runnable
{
private boolean flag=true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
}
catch (InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"ThreadStop......Exception");
flag=false;
}
System.out.println(Thread.currentThread().getName()+"ThreadStop......run");
}
}
}
注:stop方法已经过时不再使用。
9、其他方法
setPriority(int num)//设置线程优先级,默认是 5
setDaemon(boolean b)//设置守护线程
public class DaemonDemo
{
/**
* 守护线程:当前台线程结束后,守护线程run方法即使没执行完毕都结束
*/
public static void main(String[] args)
{
Thread my=new MyThread();
my.setDaemon(true);//设置守护线程
my.start();
for (int i = 0; i <1000; i++)
{
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
class MyThread extends Thread
{
public void run()
{ int i=0;
while(true)
{
System.out.println(i++);
}
}
}
join();//A线程遇到B线程join A线程让出执行权 直到B线程执行完毕再执行
setName();//设置线程名称.
toString();//返回该线程的字符串表示形式,包括线程名称、优先级和线程组