黑马程序员——多线程

---------------------- android培训、java培训、期待与您交流! ----------------------

线程:就是进程中的一个独立的控制单元线程控制着进程的执行,java jvm启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行而且这个线程运行的代码

存在于main方法中该线程称之为主线程。

先来说一说如何创建线程。

第一种创建线程的方式:

1、自定义一个类继承Thread类。

2、重写Thread类中run()方法。

3、在主线程中创建自定义类的对象并调用start()方法。

先来看下面的程序结果:

public class ThreadTest {
	public static void main(String[] args) {
		Demo demo=new Demo();
		demo.start();
		for (int i = 0; i < 3; i++) {
			try {
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()+"---"+i);
			} catch (InterruptedException e) {
				System.out.println(e.getMessage());
			}	
		}
	}
}
class Demo extends Thread{
	public void run() {
		for (int i = 0; i < 3; i++) {
			try {
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()+"---"+i);
			} catch (InterruptedException e) {
				System.out.println(e.getMessage());
			}
			
		}
	}
}
运行结果:注:结果有多种情况出现
Thread-0---0
main---0
main---1
Thread-0---1
main---2
Thread-0---2
从结果可以分析出有两个线程在运行。一个线程名为Thread-0一个线程名为main,并且这两个线程是在交替执行,也就是说这两个线程都是在获取CPU的执行权,谁先获得CPU的执行权,谁就先执行。说明了多线程是具有随机性的。

第二种创建线程的方式。

1、自定义一个类并实现Runnable接口。

2、重写Runnable接口中的run()方法。

3、在主程序中创建Thread类的对象并接收一个实现Runnable接口的子类。

4、利用Thread类的对象调用该类中的start()方法。

public class ThreadTest {
	public static void main(String[] args) {
		Demo demo=new Demo();
		Thread thread=new Thread(demo);
		thread.start();
		for (int i = 0; i < 3; i++) {
			try {
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()+"---"+i);
			} catch (InterruptedException e) {
				System.out.println(e.getMessage());
			}	
		}
	}
}
class Demo implements Runnable{
	public void run() {
		for (int i = 0; i < 3; i++) {
			try {
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()+"---"+i);
			} catch (InterruptedException e) {
				System.out.println(e.getMessage());
			}
			
		}
	}
}
运行结果:注:结果有多种情况出现

Thread-0---0
main---0
main---1
Thread-0---1
main---2
Thread-0---2

实现Runnable接口相对于继承Thread类来说有三点好处:

1)当多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效的分了,较好地体现了面向对象的设计思想。

2)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。

3)可以避免由于java的单继承特性带来的局限。

线程安全问题:

public class ThreadTest {
	public static void main(String[] args) {
			Demo demo=new Demo();
			new Thread(demo).start();
			new Thread(demo).start();
			new Thread(demo).start();
		
	}
}
class Demo implements Runnable{
	 int  ticket=10;
	public void run() {
			while(ticket>0){
				try {
					Thread.sleep(1000);
					System.out.println(Thread.currentThread().getName()+"====="+ticket--);
				} catch (InterruptedException e) {
					System.out.println(e.getMessage());
				}	
			}
		}
}
运行结果:

Thread-0=====10
Thread-1=====9
Thread-2=====8
Thread-0=====7
Thread-1=====6
Thread-2=====5
Thread-0=====4
Thread-1=====3
Thread-2=====2
Thread-1=====1
Thread-0=====0
Thread-2=====-1
从结果中可以看出,火车票号卖出了0和-1,这应该是不能出现的情况,对此上面的程序就出现了线程安全问题,这个问题在哪出现?该怎么解决这个问题呢?

可以分析出当线程0运行到Thread.sleep()时,还没打印出票号,线程1和线程2都运行到Thread.sleep(),之后依次打印出三次。会出现票号为0和-1的情况。

如果线程存在安全性的问题,为了避免这个意外的发生可以利用同步代码块进行处理。同步代码块可能出现死锁问题即两个线程对两个同步对象具有循环依赖时就会出现死锁现象。

多线程同步中synchronized关键字该如何使用?

1、放在方法上。2、放在同步块前

看如下程序,并解决了线程安全问题

public class ThreadTest {
	public static void main(String[] args) throws Exception {
		Demo demo = new Demo();
		Thread thread1 = new Thread(demo);
		Thread.sleep(1);
		Thread thread2 = new Thread(demo);
		thread1.start();
		thread2.start();
	}
}

class Demo implements Runnable {
	int ticket = 10;

	public void run() {
		while (true) {
			synchronized (this) {
				if (ticket > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						System.out.println(e.getMessage());
					}
					System.out.println(Thread.currentThread().getName()
							+ "=====" + ticket--);
				}
			}
			if(ticket<=0)
				break;
		}
	}
}
运行结果

Thread-0=====10
Thread-0=====9
Thread-1=====8
Thread-1=====7
Thread-1=====6
Thread-1=====5
Thread-1=====4
Thread-1=====3
Thread-0=====2
Thread-1=====1
死锁:

public class ThreadTest {
	public static void main(String[] args) {
		Thread thread1 = new Thread(new Demo(true));
		Thread thread2 = new Thread(new Demo(false));
		thread1.start();
		thread2.start();
	}
}

class Demo implements Runnable {
	boolean flag;

	public Demo(boolean flag) {
		this.flag = flag;
	}
	public synchronized void run() {
		if (flag) {
			while (true) {
				synchronized (MyLock.a) {
					System.out.println(Thread.currentThread().getName()
							+ "----" + "a");
					synchronized (MyLock.b) {
						System.out.println(Thread.currentThread().getName()
								+ "---" + "b");
					}
				}
			}
		} else {
			while (true) {
				synchronized (MyLock.b) {
					System.out.println(Thread.currentThread().getName() + "---"
							+ "b");
					synchronized (MyLock.a) {
						System.out.println(Thread.currentThread().getName()
								+ "--" + "a");
					}
				}
			}
		}

	}
}
class MyLock{
	static MyLock a=new MyLock();
	static MyLock b=new MyLock();
}

程序执行就锁住了。出现的原因是上面没程序代码块的锁是不同,两把锁互嵌。就如同两个吃饭,每个人只有一支筷子,谁都不相让给对方筷子导致两人都吃不了饭。对于线程互斥应该在多个同步代码块中使用同一把锁。

JDK1.5中提供了多线程升级解决方案

将同步Sysnchronized替成Lock操作

当前台进程只要还有一个在运行那么进程就不会结束,如果一个进程中只有后台进程在运行那么这个进行就会结束。前台进程是相对于后台线程而言的。如果让某个线程变成台线程,则在调用该线程对象在启用(调用start方法)之前调用setDaemon(true)方法。

线程间的通信:

可以利用Object类中的wait、notify、notifyAll这三个方法来解决线程间的通信问题。而这三个方法只能在Synchronized方法中调用,即无论线程调用一个对象的wait还是notify,该线程必须先得到该对象的锁旗标。

对于停止线程只有一种方法就是让run方法结束。

如果要实现对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据可以使用ThreadLocal

import java.util.*;
class ThreadLocalTest 
{
	public static void main(String[] args) 
	{	
		for(int i=0;i<2;i++){
		new Thread(new Runnable(){
			public void run(){
				int data=new Random().nextInt();
				ThreadData.getThreadInstance().setName("name---"+data);
				ThreadData.getThreadInstance().setAge(data);
				new A().get();
				new B().get();
			}
		
		}).start();
		}
	}
}
class A
{
	public void get(){
	ThreadData threaddata=ThreadData.getThreadInstance();
	System.out.println(Thread.currentThread().getName()+"--A:"+threaddata.getName()+"------"+threaddata.getAge());
	}
}
class B
{
	public void get(){
	ThreadData threaddata=ThreadData.getThreadInstance();
	System.out.println(Thread.currentThread().getName()+"--B:"+threaddata.getName()+"------"+threaddata.getAge());
	}
}
class ThreadData
{
	private String name;
	private int age;
	public void setName(String name){
		this.name=name;
	}
	public String getName(){
		return name;
	}
	public void setAge(int age)
	{
		this.age=age;	
	}
	public int getAge(){
		return age;
	}
	private static ThreadData ThreadInstance=null;
	private ThreadData(){
	}
	public static ThreadData getThreadInstance(){
		ThreadData ThreadInstance=tl.get();
		if(ThreadInstance==null){
			ThreadInstance=new ThreadData();
			tl.set(ThreadInstance);
		}
		return ThreadInstance;
	}
	private static ThreadLocal<ThreadData> tl=new ThreadLocal<ThreadData>();


}

实现Runnable对象之间的数据共享有三种方式

1)将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信

2)将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方式也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

3)上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

总结:要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再在同一个类中,这样比较容易实现它们直接的同步互斥和通信。

线程池的创建:
import java.util.concurrent.*;
class ThreadPoolTest 
{
	public static void main(String[] args) 
	{
		//ExecutorService threadPool=Executors.newFixedThreadPool(2);	//创建固定线程池
		//ExecutorService threadPool=Executors.newCachedThreadPool();	//创建缓存线程池
		// ExecutorService threadPool=Executors.newSingleThreadExecutor();//创建单一线程池
		for(int i=0;i<10;i++){
			final int task=i;
		threadPool.execute(new Runnable(){
			public void run(){
				for(int j=0;j<10;j++){
					try{
						Thread.sleep(20);
					}catch(Exception e){
						
					}
				System.out.println(Thread.currentThread().getName()+"--"+j+"--"+task);
				}
			}
		});
		}
		threadPool.shutdown();
	}
}

定时器的创建:

public class TimerTest {
	private static int count=0;
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		class myTimerTask extends TimerTask{
			@Override
			public void run() {
				count=(++count)%2;
				System.out.println(count);
				// TODO Auto-generated method stub
				
				System.out.println("beging");
				new Timer().schedule(new myTimerTask(),2000+2000*count);	另一个定时2+2*count秒后启动
			}		
		}
		
		new Timer().schedule(new myTimerTask(),2000);//定时器两秒后启动
		while(true){
			try {
				Thread.sleep(1000);
				System.out.println(new Date().getSeconds());	//每睡眠一秒获得当前时间秒数
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
	
}
用线程池启动定时器:
import java.util.concurrent.*;
class ScheduledThreadPoolTest 
{
	public static void main(String[] args) 
	{
		ScheduledExecutorService scheduled=Executors.newScheduledThreadPool(1);
		scheduled.scheduleAtFixedRate(new Runnable(){
			public void run(){
				System.out.println("scheduled!!!!!");
			}
		},0,2,TimeUnit.SECONDS);
	}
}


---------------------- android培训、java培训、期待与您交流! ----------------------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值