多线程在以前自学的时期,只按照教科书上敲过几个程序,对各种函数的功能理解的并不透彻,现在学习了教学视频,对这部分内容有了全新的理解。概念:首先明确其概念线程和进程是有区别的。进程是正在进行中的程序,是一个应用程序在内存中一片内存空间。而线程是指进程中能够独立执行的控制单元,线程控制着进程的执行,一个进程可以同时运行多个不同的线程。创建线程的方式:有两种:继承 Thread 类。或者 实现Runnable 接口,然后构造线程对象,把实现的类的对象作为构造线程的参数。两者都需要重写其中的run()方法,其中定义的是希望线程运行的代码。下面是实现Runnable接口的形式:
class Demo implements Runnable
{
public void run()
{
for(int x=1; x<=20; x++)
{
System.out.println(Thread.currentThread().getName()+"......"+x);
} }}
class ThreadDemo{
public static void main(String[] args) {
Demo d = new Demo(); Thread t1 = new Thread(d);//把d作为参数
构造线程对象 Thread t2 = new Thread(d); t1.start(); t2.start(); }}
这两种方法更常用的是实现接口的方法,原因是
第一种创建线程是有局限性的,因为继承不能有多个父类。
Runnable
接口的出现好处:避免了单继承的局限性。
Runnable
接口的出现,更是按照面向对象的思想,将线程要运行的任务进行单独的对象封装,降低了线程对象和线程任务的耦合性。
线程安全问题:
在多线程运行的实际情况中,在有多个线程访问出现延迟,加上线程运行的随机性,导致
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
举例说明:
如果用如下代码
实现
多线程卖票系统:
/*
多线程实现多个窗口卖票。
*/
class Ticket implements Runnable//extends Thread
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
//显示线程名及余票数
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
//创建Runnable接口子类的实例对象
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();
}
}
就会出现tick这个共享数据出现错误,此时需要解决安全问题就需要用同步的方法。
同步也有两种方法实现:
第一种是 同步代码块,格式
synchronized (任意对象)
{
需要被同步的代码
}
此时对象这个参数就相当于一把锁,获得锁的线程进行代码块的执行,没有锁的线程即使有cpu执行权,也无法对代码块进行执行,执行结束后释放锁,保证对共享数据操作的安全性。
修改后代码如下
class Ticket implements Runnable
{
private int tick=100;
Object obj = new Object();
public void run()
{
while(true)
{
//同步代码块
synchronized(obj)
{
if(tick>0)
{
try
{
//使用线程中的sleep方法,模拟线程出现的安全问题
//因为sleep方法有异常声明,所以这里要对其进行处理
Thread.sleep(10);
}
catch (Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..tick="+tick--);
}
}
}
}
}
第二种是同步函数,只需在需要同步的函数用synchronized修饰即可。同步代码块需要建立一个任意对象作为锁,而同步函数则默认本类对象的引用为锁,就是this。
格式如下
class Ticket implements Runnable
{
private int tick=100;
Object obj = new Object();
public void run()
{
while(true)
{
show();
}
}
public synchronized void show()//直接在函数上用synchronized修饰即可实现同步
{
if(tick>0)
{
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..tick="+tick--);
}
}
}
对于静态函数的同步需要注意,静态函数使用的锁是本类对象加载过程中生成的.class字节码文件,在synchronized参数里改为(类名.class)即可。
注意:同步函数的前提,是保证共享数据正确处理的条件,有两条。1.必须有两个或两个以上的线程 2.这些线程必须使用同一个锁。后者往往容易是出错的地方!
死锁:死锁指的在同步出现嵌套的时候,两个或两个以上线程同时在等待另一个已经上锁的同步代码块。
下面是死锁的代码示例:
class Lock
{
static Object lock1 = new Object();
static Object lock2 = new Object();
}
class Run implements Runnable
{
private boolean flag ;
Run(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag == true)
{
synchronized(Lock.lock1)
{
System.out.println("true lock1");
synchronized(Lock.lock2)
{
System.out.println("true lock2");
}
}
}
else
{
synchronized(Lock.lock2)
{
System.out.println("false lock2");
synchronized(Lock.lock1)
{
System.out.println("false lock1");
}
}
}
}
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Run(true));
Thread t2 = new Thread(new Run(false));
t1.start();
t2.start();
}
}
线程间的通信:
在多个线程同时对共享数据进行操作,但是操作的动作不同时,就要进行线程间通信,协调操作。
下面是典型的消费者生产者问题,对“产品”进行生产和输出,要求生产一个消费一个。
代码如下:
class Res//表示资源 就是产品
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag){
try{
wait();
}catch(InterruptedException e){
}
}
this.name = name + "--" + count++;
flag = true;
this.notifyAll();//唤醒等待线程
System.out.println(Thread.currentThread().getName() +"--producer" + this.name);
}
public synchronized void out()
{
while(!flag){
try{
wait();
}catch(InterruptedException e){
}
}
flag = false;
this.notifyAll();
System.out.println(Thread.currentThread().getName() +"--consumer--" + this.name);
}
}
class Producer implements Runnable
{
private Res r;
public Producer(Res r)
{
this.r = r;
}
public void run()
{
for(int i = 0; i<100; i++)
{
r.set("--goods-----");//生产产品 set()函数设置属性
}
}
}
class Consumer implements Runnable
{
private Res r;
public Consumer(Res r)
{
this.r = r;
}
public void run()
{
for(int i = 0; i<100; i++)
{
r.out();//输出产品信息,表示消费产品
}
}
}
class ProducerConsumerTest
{
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(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
此外JDK1.5中提供了多线程升级解决方案。将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
多线程这部分知识十分重要,对多条任务之间协调的思维是一种锻炼,以上。
多线程这部分知识十分重要,对多条任务之间协调的思维是一种锻炼,以上。