多线程
进程:正在进行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的单元,线程控制着进程的执行,一个进程中至少有一个线程。
1.如何在自定义的代码中定义一个线程
通过对API的查找,Java已经提供了对线程这I类事物描述,Thread类。
创建线程的第一种方式:继承Thread类
步骤:1、定义类继承Thread
2、复写Thread类中的run方法;
3、 调用线程的start方法
该方法有两个作用:启动线程,调用run方法:
package thread;
public class Demo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadDemo td= new ThreadDemo();//创建线程
//d.run();仅仅是对象方法的调用,是单一的线程会先执行run再执行for语句结果不会变化
td.start();//开启线程,调用run方法
for(int j=0;j<100;j++)
System.out.println("main......"+j);
}
}
class ThreadDemo extends Thread
{
public void run(){//run方法存储要运行的代码
for(int i=0;i<100;i++)
System.out.println("run-------"+i);
}
}
每一次的运行结果可能有点不同
这是因为多个线程都获取CPU的执行权。CPU执行到谁,谁就运行。明确一点的是,在某一时刻,只能有一个程序在运行(多核CPU除外),CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的吧多线程的运行行为在互相争抢CPU的执行权。
这就是多线程的一个特性:随机性,谁抢到谁执行,至于执行多长,CPU说的算。
2.为什么要覆盖run方法?
Thread类描述线程,该类就定义了一个功能,用于存储要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码
ThreadDemo th= new ThreadDemo();//创建线程
//th.run(); 仅仅是一个对象的方法调用,是单一的线程,先执行run在执行主线程下面的语句
th.start();//开启线程,调用run方法
th.start();//开启线程,调用run方法
for(int i=0;i<100;i++)
.......
3.普通方法
每个线程都有自己默认的方法
Thread-编号 该编号从0开始
static Thread.currentThread() 获取当前线程对象(相当于this)
getName 获取线程名称
设置线程名称的方法:setName()或构造函数
package thread;
public class ThreadDemo2 {
/**
* 2个线程与主线程交替运行
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Demo2 d= new Demo2("线程1");
Demo2 d1= new Demo2("线程2");
d.start();
// d.start();出错,IllegalThreadStateException
d1.start();
for(int i=0 ;i<100;i++)
System.out.println("main"+"====="+i);
}
}
class Demo2 extends Thread{
//private String name;
Demo2(String name){
super(name);
}
public void run(){
for(int i=0;i<100;i++)
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"-----"+i);
//获取当前线程,标准写法:Thread.currentThread()
}
}
4.创建线程的第二种:实现Runable接口
步骤:
1)定义类实现Runnable接口
2)覆盖Runnable接口中的run方法
将线程运行的代码存放在该run方法中
3)通过Thread类建立线程对象
4) 将Runnable接口的子类对象方法作为实际对象参数传给Thread的构造函数
5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
package thread;
public class ThreadTest1 {
/**
* 一个简单的买票引出第二种定义线程的方法
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/* Ticket t1= new Ticket();
Ticket t2= new Ticket();
Ticket t3= new Ticket();
Ticket t4= new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();*/
//出现第二种创建线程方式,一般推荐使用这种
Ticket ticket= new Ticket();
Thread t1= new Thread(ticket);
Thread t2= new Thread(ticket);
Thread t3= new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable//extends Thread
{
//private static int ticket=100 ;//不静态修饰,每new一个线程,都各自卖100张票,实际总共才100张票
private int ticket=100 ;
public void run(){
while(true){
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"..."+ticket--);
}
}
}
该创建方式(实现)和继承方式有什么区别
避免了单继承的局限性;
两种创建线程的区别:
继承:线程代码存放于子类的run方法中
实现接口:线程代码存在接口的子类run方法中
6.安全问题
当多条语句在操作通一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据错误。
解决方式:同步代码块
synchronized(对象)
{
需要被同步的代码
}
package thread;
//银行有金库,2个储户各存300员,每次每人存100元
public class ThreadTest2 {
public static void main(String[] args) {
Cus cus= new Cus();
Thread t1= new Thread(cus);
Thread t2= new Thread(cus);
t1.start();
t2.start();
}
}
class Bank//查找发现多个线程的共享数据是num
{
private int num=0;
// Object obj= new Object();
public synchronized void add(int money){
/*synchronized (obj) {
num=num+money;
System.out.println(Thread.currentThread().getName()+"num="+num);
}*/
num=num+money;
System.out.println(Thread.currentThread().getName()+"...num="+num);
}
}
class Cus implements Runnable
{
private Bank bank= new Bank();
public void run(){
for(int i=0;i<3;i++){
bank.add(100);
}
}
}
上述中的对象如同锁,持有锁的线程可以再同步中执行,没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。
同步的前提:
1.必须要有两个或两个以上的线程
1.必须要有两个或两个以上的线程
2.必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题
弊处:多个线程需要判断锁,较为消耗资源
注意: 非静态同步函数的锁是this而静态同步函数的锁不是this,因为是静态的优先于对象的加载,锁是类名.class
package thread;
public class ThreadTest3 {
/**
* 多线程的懒汉式并发加载的单例模式
* @param args
*/
private static ThreadTest3 s= null;
private ThreadTest3(){}
public static ThreadTest3 getInstance(){
if(s==null)
//双重否定稍微提高效率,从第三次开始,判断——不为null直接return,效率提高
{
synchronized(ThreadTest3.class)//静态的方法,锁是方法对应的类的字节码文件的对象
//第二个线程没有锁,必须等第一个执行完
{
if(s==null)//第一个线程开始执行有可能停止在这,所以加锁
s=new ThreadTest3();
}
}
return s;
}
}