Java Thread入门殿堂
线程介绍
Thread,do you know?线程是程序执行的一条路径, 一个进程中可以包含多条线程;多线程并发执行可以提高程序的效率, 可以同时完成多项工作。线程在并发编程中非常重要,巧妙的使用多线程技术大大有利于程序的效率,但是线程的使用是有门槛的,用的不好适得其反,综上:掌握线程技术乃程序猿的必备灵药!
线程的创建
I 线程的创建常用的有两种方式:
方式一:继承Thread类,重写run方法。
方式二:实现Runnable接口,重写run方法。
这里只给出方式二的实现代码清单:
Runnable底层源码分析:在Thread构造函数中传入Runnable实现对象,在Thread源码中将Runnable对象target传递给init方法,而在init方法中又将target传给的Thread类的成员变量this.target中,最后在run方法中,通过判断this.target是否为null而选择执行Runnable中的run方法与否。详细见下图:
II 匿名内部类实现:
方式三:匿名内部类实现Thread创建,通俗的来讲就是一种一次性使用的产物,写起来非常方便很适合实际开发中一次性使用。匿名内部类见下图:
III Callable实现
Callable实现方法与Runnalbe相似,但是Callable实现方法有返回值,Callable中重写的是call方法,Callable常用在Java自带的线程池ExecutorService中,我将在本博客尾部介绍ExecutorService线程池,这里先简单列出Callable实现类,请在线程池中看具体使用。
线程命名
设置名字:①在创建的时候设置;②创建后利用thread.setName()设置;
获取名字:thread.getName()获取;Thread.CurrentThread().getName()获得当前线程的名字。
下图为线程的命名方法截图:
线程操作(阻塞、激活、同步等)
(1)守护线程
守护线程:利用setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出。【注】:守护线程要在线程启动前设置才有效哟!
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.setDaemon(true); //将t1设置为守护线程
t1.start();
(2)线程暂停join&yeild
线程暂停:* join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续;join(int), 可以等待指定的毫秒之后继续。 * yeild(),又称礼让线程,使当前线程让出CPU,但仍有机会被CPU分配时间片进入就绪队列。
final Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
if(i == 2) {
try {
//t1.join(); //插队,加入(t2停下来等待t1执行完毕)
t1.join(30); //加入,有固定的时间,过了固定时间,继续交替执行
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + "...bb");
}
}
};
t1.start();
t2.start();补充:setPriority()设置线程的优先级,但是究竟起没起作用还要看JVM的心情哟!
(3)线程同步&死锁
I:线程同步
什么情况下需要同步
* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
同步代码块
* 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
* 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的class Printer {
Demo d = new Demo();
public static void print1() {
synchronized(d){ //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
System.out.print("C");
System.out.print("S");
System.out.print("D");
System.out.print("N");;
System.out.print("\r\n");
}
}
public static void print2() {
synchronized(d){
System.out.print("博");
System.out.print("客");
System.out.print("知");
System.out.print("飞"); System.out.print("翀");
System.out.print("\r\n");
}
}
}
【注】:使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的。非静态同步函数的锁是:this; 静态的同步函数的锁是:字节码对象。
代码清单:
class Printer {
public static void print1() {
synchronized(Printer.class){
//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
System.out.print("C");
System.out.print("S");
System.out.print("D");
System.out.print("N");
System.out.print("\r\n");
}
}
public static synchronized void print2() {
System.out.print("知");
System.out.print("飞");
System.out.print("翀");
System.out.print("\r\n");
}
}
II:死锁
多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁,所以要尽量不要嵌套使用。
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread() {
public void run() {
while(true) {
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "开吃");
}
}
}
}
}.start();
}
(4)线程安全
* 多线程并发操作同一数据时, 就有可能出现线程安全问题
* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
常用Java类的线程安全性
Vector是线程安全的, ArrayList是线程不安全的;
StringBuffer是线程安全的, StringBuilder是线程不安全的;
Hashtable是线程安全的, HashMap是线程不安全的;
线程中的单例设计模式
Singleton设计模式:保证类在类村中只有一个对象,该模式的关键在于:(1)构造方法私有private;(2)在本类中定义一个本类对象,Singleton s;(3)提供公共的访问方法,public static Singleton getInstance(){ return s; }
单例模式有以下三种实现方式:(这对之后的框架学习非常有用,千万理解!)
方式一:饿汉式(类加载及创建Singleton s----导致程序启动较慢但运行较快)
方式二:懒汉式又称单例的延迟加载模式(使用到类对象时才创建Singleton s----导致程序启动较快但运行较慢)
方式三:final定义(理解:final修饰的变量无法被修改)
Runtime类
java中Runtime类就是一个饿汉单例类,源码截图如下:
![]()
Runtime类的使用(实现向DOS窗口下输入命令的效果):
Runtime r = Runtime.getRuntime();
//r.exec("shutdown -s -t 300"); //300秒后关机
r.exec("shutdown -a"); //取消关机
计时器(Timer类)
Timer类:用来计时的java类,可以定时开启任务线程,并设置间隔多长时间启动一次。代码清单如下图:
多线程通信
两个线程通信:
1.什么时候需要通信
* 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
* 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
* 2.怎么通信
* 如果希望线程等待, 就调用wait()
* 如果希望唤醒等待的线程, 就调用notify();
* 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用三个及以上通信:
* notify()方法是随机唤醒一个线程
* notifyAll()方法是唤醒所有线程
* JDK5之前无法唤醒指定的一个线程
* 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件(通知无法指定到那个线程上只有while能读到)
线程的生命周期
互斥锁(JDK1.5新特性)
1.同步
* 使用ReentrantLock类的lock()和unlock()方法进行同步
* 2.通信
* 使用ReentrantLock类的newCondition()方法可以获取Condition对象
* 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
* 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了【注】:JDK1.5中使用Reentrant来实现同步与通信,比synchronized&wait/notify更加高效,但是写起来更加麻烦;Condition可以理解为具有标识的wait/notify,它可以通知到某个具体Condition头上来,比wait/notify更加高效。
线程组
线程组概述
* Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
* 默认情况下,所有的线程都属于主线程组。
* public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
* public final String getName()//通过线程组对象获取组的名字
* 我们也可以给线程设置分组
* 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
* 2,创建线程对象
* 3,Thread(ThreadGroup group, Runnable target, String name)
* 4,设置整组的优先级或者守护线程
案例演示
* 线程组的使用,默认是主线程组
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "张三");
Thread t2 = new Thread(mr, "李四");
//获取线程组
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通过结果我们知道了:线程默认情况下属于main线程组
// 通过下面的测试,你应该能够看到,默认情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());自己定义线程组
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");MyRunnable mr = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, mr, "张三");
Thread t2 = new Thread(tg, mr, "李四");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
Java内置线程池
线程池概述
* 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
内置线程池的使用概述
* JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
* public static ExecutorService newFixedThreadPool(int nThreads)
* public static ExecutorService newSingleThreadExecutor()
* 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
使用步骤:
* 创建线程池对象
* 创建Runnable实例
* 提交Runnable实例
* 关闭线程池
下面以求前n项和为例引入ExecutorService&Callable来实现:
谢谢阅读 ----知飞翀