一,在学习多线程之前,先来看看涉及到的概念。
进程(process):是操作系统运行的一个任务,一个应用程序运行在一个进程中。
线程(Thread):一个进程对应一块内存区域。操作系统系统会把进程划分为一个个小的功能单元。而进程中所包含的一个或多个这样的功能单元称为线程。
注意:
1.一个进程会有一个私有的虚拟空间,而这个空间只能被它包含的线程访问。
2.一个线程只能属于一个进程。
3.当操作系统在创建一个进程时,该进程所包含的线程中会存在一个主线程。
并发:并发是怎么回事呢?我们所谓并发通常是指多个线程“同时”运行。
实际上,当一个进程创建后,OS(操作系统)会将时间分成许多时间片并且均匀的分给每个线程。当某个线程获得时间片后开始运行,其他线程则处于等待状态。当这个线程获得时间片结束后,不管这个线程运行什么地方,都会停下来。然后另外一个获得时间片的线程继续运行,如此下去,直到任务完成。
所以从微观的角度来看,这些线程并没有同时运行,会存在时间的先后顺序,总是”走走停停“。那么从宏观的角度来看(这些时间片非常小,我们通常感受不到差异)这些线程是在“同时”运行,就是我们通常所说的并发。
守护线程:该线程和普通的线程基本上没什么区别。唯一的区别是,当进程中只剩下守护线程时,所有守护线程全部终止。我们只需要通过Thread提供的方法来设定即可,利用void setDaemon(boolean param) 当参数param为true时该线程便被设为守护线程。
eg: GC就是运行在一个守护线程上的。
注:守护线程也叫做后台线程,其守护着前台线程,当前台线程全部结束后,守护线程也随之结束。
二,接下来我们一起看看线程Thread所拥有的一些方法:
1.获取当前线程的方法
Thread.currentTread() eg: Thread t = Thread.currentThread();
2.获取线程信息的方法
方法 | 返回类型 | 返回内容 |
---|---|---|
getId() | long | 返回该线程的标识符 |
getName() | String | 返回该线程的名称 |
getPriority() | int | 返回线程的优先级 |
getState() | Thread.state | 返回该线程的状态 |
isAlive() | boolean | 测试线程是否处于活动状态 |
isDaemon() | boolean | 测试线程是否为守护线程 |
isInterrupted() | boolean | 测试线程是否已经中断 |
3,线程的其他方法
方法 | 作用 |
---|---|
static void sleep(long s) | Thread的静态方法sleep用于使当前线程进入阻塞状态。 该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会重新进入到Runnable状态,等待分配时 间片。
该方法生命抛出一个InterruptException。所以在使用该方法时要捕获这个异常。 |
static void yield() | 该方法用于使当前线程主动让出cpu时间片片会到runnable的状态,等待分配时间片。 |
void join() | 该方法用户等待当前线程结束。 该方法声明抛出InterruptException |
注:一个方法中定义了一个内部类,该内部类若想引用这个方法中的其他局部变量,那么这个变量必须是final的。
三,线程的优先级
一个时间片要分配给那个线程,以及这个线程结束后将要运行那个线程。这些线程之间的切换是由线程调度控制的,我们不能通过代码控制,可是只要我们把一个线程的优先级设的高点,那么这个线程获得时间片的概率就会大一点,运行概率也会大一点。
线程的优先级通常被分为10级,用1——10来表示,1最低,10最高。线程有3个常量来表示最高,最低,默认优先级,分别是:
Thread.MIN_PRIORITY 最低优先级
Thread.MAX_PRIORITY 最高优先级
Thread.NORM_PRIORITY 默认优先级
设置优先级方法为:void setPriority(int priority);
package thread;
/**
* 线程优先级
* 1——10
* 理论上,线程优先级高的线程,
* 被分配到时间片段的次数多
* @author Administrator
*
*/
public class ThreadDemo6 {
public static void main(String[] args){
Thread max = new Thread(){
public void run(){
for(int i=0;i<5000;i++){
System.out.println("max");
}
}
};
Thread normal = new Thread(){
public void run(){
for(int i=0;i<5000;i++){
System.out.println("normal");
}
}
};
Thread min = new Thread(){
public void run(){
for(int i=0;i<5000;i++){
System.out.println("min");
}
}
};
max.setPriority(Thread.MAX_PRIORITY);
normal.setPriority(Thread.NORM_PRIORITY);
min.setPriority(Thread.MIN_PRIORITY);
min.start();
normal.start();
max.start();
}
}
四,创建线程的方式
1,继承Thread类,重写run方法
package thread;
/**
* 第一种创建线程的方式
* 继承Thread类,重写run()方法
* @author Administrator
*
*/
public class ThreadDemo1{
public static void main(String[] args){
//有先后顺序运行的方式是同步运行。
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/**
* start()方法用于将线程纳入到线程调度
* 这时,线程进入到runnable状态,等待
* 线程调度分配时间片段。
* 当线程调度将时间片段分配给当前线程,该线程的run()方法才开始执行。
* 直到线程的run()方法执行完毕,线程结束最终被收回。
* 在线程的run()方法执行期间,该线程处于走走停停。
*/
t1.start();
t2.start();
}
}
/**
* 解耦 耦合
* @author Administrator
*/
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊");
}
}
}
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("我是查水表的");
}
}
}
public static void main(String[] args){
//有先后顺序运行的方式是同步运行。
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/**
* start()方法用于将线程纳入到线程调度
* 这时,线程进入到runnable状态,等待
* 线程调度分配时间片段。
* 当线程调度将时间片段分配给当前线程,该线程的run()方法才开始执行。
* 直到线程的run()方法执行完毕,线程结束最终被收回。
* 在线程的run()方法执行期间,该线程处于走走停停。
*/
t1.start();
t2.start();
}
}
/**
* 解耦 耦合
* @author Administrator
*/
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊");
}
}
}
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("我是查水表的");
}
}
}
2,定义线程体,实现Runnable接口
package thread;
/**
* 第二种创建线程的方式
* 定义线程体Runnable
* @author Administrator
*
*/
public class ThreadDemo2 {
public static void main(String[] args){
Runnable run1 = new MyRunnable1();
Runnable run2 = new MyRunnable2();
Thread t1 = new Thread(run1);
Thread t2 = new Thread(run2);
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("报暗号");
}
}
}
class MyRunnable2 implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("宝塔镇河妖");
}
}
}
五,线程存在的问题
1.要将异步变成同步。利用synchronized关键字
多个线程并发读写同一个临界资源时会发生“线程并发安全问题。
常见的临界资源:
多线程共享实例变量
多线程共享静态公共变量
要解决线程安全问题,要将异步操作变为同步操作。
异步操作: 多线程并发的操作,相当于各干各的
同步操作: 有先后顺序的操作,相当于你干完我再干
synchronized关键字是java中的同步锁
package thread;
/**
* 线程并发问题
* @author Administrator
*
*/
public class SyncDemo {
//桌子上有20个豆沙包
public static int beans = 20;
public static void main(String[] args){
Thread t1 = new Thread(){
public void run (){
int bean = 0;
while(true){
bean = getBean();
System.out.println(this.getName() + bean+"个豆沙包");
}
}
};
Thread t2 = new Thread(){
public void run (){
int bean = 0;
while(true){
bean = getBean();
System.out.println(this.getName()+":剩下"+bean+"个豆沙包");
}
}
};
t1.start();
t2.start();
}
//每次从桌子上取一个豆沙包
public static synchronized int getBean(){
if(beans == 0){
throw new RuntimeException("没有豆沙包");
}
Thread.yield();
return beans;
}
}
2.性能问题
加了同步锁synchronized后执行效率变低,性能变差为了最低限度的降低同步锁synchronized的影响。在保证线程安全的情况下,尽可能减小synchronized作用的范围。
同步代码块(synchronzied关键字),同步代码块包含两部分:
一个作为锁的对象的引用,一个作为由这个锁保护的代码块。
synchronized(同步监视器——锁对象引用){
//代码块
//代码块
}
若方法中的所有代码都需要同步也可以给方法直接加锁。
每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出代码时会自动释放锁,而且无论是通过正常途径退出还是通过抛异常退出都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
注:多线程看到的是同一个对象才有同步作用(锁对象必须是同一个)如果synchronized块在一个非静态方法中,通常锁对象用this
package thread;
public class SyncDemo2 {
// private static Object obj = new Object();
public static void main(String[] args){
final SyncDemo2 demo = new SyncDemo2();
Thread t1 = new Thread(){
public void run(){
demo.buy(getName());
}
};
Thread t2 = new Thread(){
public void run(){
demo.buy(getName());
}
};
t1.start();
t2.start();
}
//场景,某个商场只有一个停车位,而有多辆车来停
public void buy(String name){
System.out.println(name+"正在找车位");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"找到车位了");
synchronized(this){
System.out.println(name+"正在停车");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"车开离车位了");
}
System.out.println(name+"车离开这个停车场了");
}
}
3.线程之间协同工作
wait和notify
例如:浏览器的一个显示图片的 displayThread想要执行显示图片的任务,必须等待下载线程。downloadThread将该图片下载完毕。
如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread,可以显示图片了,这时display继续执行。
以上逻辑简单的说法就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切相关的。
注:在downloadThread完成之后,如果有多个线程要被通知(解除锁),用notify会随机通知(解除)这多个线程(以任意顺序)。如果用notifyAll()的活,可以一次全部通知这些等待线程。
我们调用哪个对象的wait()和notify,就应当对当前对象加锁,锁的就是这个对象。
package thread;
/**
* 线程协同工作
* @author Administrator
*
*/
public class ThreadDemo10 {
//表示图片是否下载完毕
public static boolean isFinish;
public static Object obj = new Object();
public static void main(String[] args){
final Thread download = new Thread(){
public void run(){
System.out.println("down:开始下载图片");
for(int i=1;i<=100;i++){
System.out.println("down:已完成"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("图片下载完毕");
isFinish = true;
/**
* 当图片下载完毕,就应当通知显示图片的线程开始工作了
*/
synchronized(obj){
obj.notify();
}
System.out.println("down:开始下载附件");
for(int i=1;i<=100;i++){
System.out.println("down:已完成"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("附件下载完毕");
isFinish = true;
}
};
/**
* mian()方法中定义了一个内部类show,
* 该内部类若想引用main方法中的其他局部变量
* 那么这个变量必须是final的
*/
Thread show = new Thread(){
public void run(){
System.out.println("show:开始图片显示...");
//哈哈哈
try {
// download.join();
//在obj对象上等待
synchronized(obj){
obj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!isFinish){
throw new RuntimeException("图片没有下载完成");
}
System.out.println("show:图片显示完毕");
}
};
download.start();
show.start();
}
}
4,将一些非线程安全的集合转换为线程安全的集合
利用collections类
//将现有的list集合转换为线程安全的
List<String> list = new ArrayList<String>();
list = Collections.synchronizedList(list);
System.out.println(list);
//将现有的set集合转换成线程安全的
Set<String> set = new HashSet<String>();
set = Collections.synchronizedSet(set);
//将现有的map集合转换成线程安全的
Map<String,Object> map = new HashMap<String,Object>();
map = Collections.synchronizedMap(map);
六,线程池
使用ExecutorService实现线程池,是java提供用来管理线程的线程池的类
作用:控制线程数量、重用线程
当一个程序中创建大量线程,并在任务结束后销毁,会过度消耗资源,以及过度切换线程的危险,从而导致系统崩溃。为此我们应使用线程池来解决这个问题。
线程池有以下几种实现策略:
方法 | 说明 |
---|---|
Executors.newCachedThreadPool( ) | 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。 |
Executors.newFixedThreadPool(int nThreads ) | 创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。 |
Executors.newScheduledThreadPool(int corePoolSize) | 创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行。 |
Executors.newSingleThreadExecutor() | 创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。 |
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池
* 用于重用线程以及控制线程数量
* @author Administrator
*/
public class ThreadPoolDemo {
public static void main(String[] args){
ExecutorService threadPool = Executors.newFixedThreadPool(2);
for(int i=0;i<5;i++){
Runnable runn = new Runnable(){
public void run(){
for(int i=0;i<3;i++){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
threadPool.execute(runn);
}
}
}
声明:该线程学习笔记是在我学习多线程之后完成的,其中用到一些他人的代码思路,向您致敬。目的是在有网的情况下能随时查看多线程知识,也可以让更多的小伙伴看到,不做任何商业用途,如有侵权之处,请联系我,以便删改。