(6)简单说说java中的线程
先甩出来两种创建线程的方法:
private static int count = 100; public static void main(String[] args) {
// 用继承Thread类的方式启动一个线程
new Thread() {
public void run() {
synchronized (StartThreadTest.class) {
while (count > 0) {
count--;
System.out.println(Thread.currentThread() + "卖了一张票,还剩" + count);
}
}
}
}.start(); // 用实现Runnable接口的方式启动一个线程
new Thread(new Runnable() {
public void run() {
synchronized (StartThreadTest.class) {
while (count > 0) {
count--;
System.out.println(Thread.currentThread() + "卖了一张票,还剩" + count);
}
}
}
}).start();
}
不只是线程,在这个javase标签下的所有的笔记都是一些核心的点,甚至有些比较小的内容这里略过,就没有介绍。
线程
线程:在一个进程中负责了代码的执行,就是进程中的一个执行路径
多线程:在一个进程中有多个线程在同时执行不同的任务。
一个java应用程序至少有几个线程?至少有2个线程,一个是主线程负责了main方法的执行,一个是gc()垃圾回收器的执行,他们是互不干扰的。
多线程的特点:
1、解决了一个进程同时执行多个进程的问题
2、不能提高效率,只是提高了cpu资源的利用率。
3、引发了线程的安全问题
4、会出现死锁现象
如何创建多线程:
创建线程方式一:
1、自定义一个类继承Thread类。
2、重写Thread类的run()方法,run()方法中是该线程的任务代码。自定义线程的任务代码写到run()方法中。Jvm创建的主线程的代码,就是main方法中的所有代码。
3、创建自定义线程。并且调用start()方法开启线程。一旦一个线程开启(start)就会执行run()方法,直接调用run()方法,只相当于调用了一个普通的方法。
方式二:
方式二往后面翻
线程的生命周期
其实就是介绍线程的创建、可运行状态、运行状态、阻塞状态、死亡状态,五中状态之间的相互切换。
常见线程的方法
Thread(String name) 初始化线程的名字
getName() 返回线程的名字
setName(String name) 设置线程对象名
sleep() 线程睡眠指定的毫秒数。Static型的,谁执行sleep这句代码,谁睡着,可以t.sleep()调用,也可以Thread.sleep()
getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
setPriority(int newPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
currentThread() 返回CPU正在执行的线程的对象
注意了在从写run方法的时候使用try-catch捕获处理而不使用throws处理,不然会出错,引原来的父类Thread的run方法就没有throws错误。
创建线程方式二:使用Runnable创建线程
1、定义实现Runnable接口的类class myrun inplements Runnable...
2、重写Runnable接口中的run方法,就是将线程运行的代码放入run中
3、通过通过Thread类建立线程对象 Thread t = new (Runnable run);
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法。
5、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。
是这样的,Runnable接口只有一个方法就是run(),别的什么都没有了,包括start()方法,所以要启动其run方法必须使用Thread相关的方法,于是就把,Runnable的子类对象传递给Thread构造出来一个线程然后再启动。这就好比说,Thread是一个工人,其工作是Runnable分配的。
注意事项:
1、Runnable实现类的对象是线程对象吗?
Runnable实现类的对象并不是一个线程的对象,只不过是实现了Runnable接口的一个对象而已罢了。只有Thread或者是Thread的子类才是线程的对象。
2、为什么把Runnable实现类的对象作为实参传递给Thread的对象,其作用是什么?
其作用就是把Runnable实现类的run方法作为线程的任务去执行的。
比较推荐使用第二种Runnable的方法创建线程,这种方法创建线程。其中最大的一个优势是,可以实现所继承呀。
线程的同步机制
两种方式,其实同步机制有很多内容,比如还有Lock锁和reetrantLock锁以及条件对象,具体可以参看javacore。这里只讲了synchronized的两种形式
同步机制方式一:同步代码块
synchronized(锁对象)
{
需要同步的代码块...
}
同步代码块需要注意的事项:
1.任意个一个对象都可以作为锁对象
2.在同步代码块中调用sleep函数,并不会释放锁对象。
3.只有真正存在线程安全问题才需要使用同步代码块,不然的话降低代码执行的效率,不然每次都要判断是否加锁。
4.多线程操作的多对象必须是唯一共享的否则是无效的。所以这个锁对象需要定义为 static型的,比如static Object o = new Object();注意了最简单的加锁方式:synchronized(“锁”){}这种形式你也能所得住,”锁”在常量池中只有一个,所有能够达到共享锁的作用。如果是 synchronized(new String(“锁”)){}这样的就不行了,new String(“锁”)得到的不是字符串常量中的不变字符,每次都会在堆内存中创建一个”锁”。
同步机制方式二;同步函数
同步函数的注意事项:
1、如果是一个非静态的同步函数的锁 对象是this,如果是静态的同步函数锁 对象是当前函数所属的类的字节码文件(Class对象----专门用来描述编译之后的字节码文件,说明为什么会想着有Class,Class对象里面维护的是一个类的信息,不如类的方法有哪些访问属性等等,直接查看对应的Class对象即可)。这里具体分析一下,非静态函数每个线程进来,线程自身的this对象就是当前的锁,是锁不住共享资源的。这个时候把我们要同步的函数定义成static形式的,这样的锁,使用的是这个类对应的class对象
修饰符 synchronized 返回值类型 函数名(参数列表...
{
}
综合比较以上两种同步方式我们推荐使用第一种同步代码块,而不是用第二种同步代码函数,其原因如下:
1、同步代码块的对象我们可以自由的指定,方便控制,同步函数的锁对象是固定的(就两种,上面说明的有),不能由我们来制定,我们做多自己来选择。
2、同步代码块可以很方便的来控制需要被同步的代码范围,同步函数必须是整个函数的所有代码都同步了。但往往一个函数中并不是多有的代码都需要同步,这样就会影响整体的效率。
死锁现象
代码同步机制实现了共享数据的安全性,但是带来了死锁问题。死锁问题中的经典问题是“哲学家就餐问题”。
死锁想象出现根本原因:
1、存在了两个或两个以上的线程
2、存在两个两个以上的共享资源。
从技术层面来说没有解决的方案,只能尽可能的避免,也就是尽量避免上面的两种情况。
public class DeadLock {
public static void main(String[] args) {
new Thread(new Runnable() { // 创建线程, 代表中国人
public void run() {
synchronized ("刀叉") { // 中国人拿到了刀叉
System.out.println(Thread.currentThread().getName()
+ ": 你不给我筷子, 我就不给你刀叉");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("筷子") {
System.out.println(Thread.currentThread()
.getName() + ": 给你刀叉");
}
}
}
}, "中国人").start();
new Thread(new Runnable() { // 美国人
public void run() {
synchronized ("筷子") { // 美国人拿到了筷子
System.out.println(Thread.currentThread().getName()
+ ": 你先给我刀叉, 我再给你筷子");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("刀叉") {
System.out.println(Thread.currentThread()
.getName() + ": 好吧, 把筷子给你.");
}
}
}
}, "美国人").start();
}
}
线程通信
线程通信:一个线程完成了自己的任务的时候,要通知另外一个线程去完成另外一个任务。
最最经典的例子就是“生产者与消费者”的例子。我觉着这个例子适合多次看,特别的熟悉。
Wait():等待 如果线程执行了wait方法那么该线就会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒
Notify():唤醒 唤醒线程池中等待的线程之一方法
notifyAll()唤醒 唤醒等待线程池中所有等待的线程
Wait()和notify()方法的注意事项
1、wait方法和notify方法属于Object类的方法
2、Wait方法和notify方法必须在同步代码块或者是同步函数中才能使用
3、Wait()方法和notify()方法必须要有由调用,否则也会调用的
Wait()方法:一个线程如果执行了wait方法,那么该线程就会进入一个以锁对象为标识符的线程池中。一旦调用wait方法会自动的释放锁。
Notify():如果一个线程执行notify方法,那么就唤醒以锁对象为标识符的线程池中等待线程中的其中一个。
为什么把wait方法和notify方法设计到Object上面?因为只有锁对象才调用这两个方法,而任意的对象都可以作为锁对象
为什么在同步代码块或者是同步函数中调用wait方法和notify方法?因为只有同步代码块才会使用锁对象,而只有锁对象才会调用wait和notify方法
消费者生产者的例子:
public class Demo10 {
public static void main(String[] args) {
Person p = new Person();
Producer pro = new Producer(p);
Consumer con = new Consumer(p);
Thread t1 = new Thread(pro, "生产者");
Thread t2 = new Thread(con, "消费者");
t1.start();
t2.start();
}
} // 使用Person作为数据存储空间
class Person {
String name;
String gender; public synchronized void set(String name, String gender) {
this.name = name;
this.gender = gender;
} public synchronized void read() {
System.out.println("name:" + this.name + "----gender:" + this.gender);
} } // 生产者
class Producer implements Runnable {
Person p; public Producer() { } public Producer(Person p) {
this.p = p;
} @Override
public void run() {
int i = 0;
while (true) { if (i % 2 == 0) {
p.set("jack", "man");
} else {
p.set("小丽", "女");
}
i++; } } } // 消费者
class Consumer implements Runnable {
Person p; public Consumer() { } public Consumer(Person p) {
this.p = p;
} @Override
public void run() { while (true) {
p.read(); }
} }
wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll:唤醒持有同一监视器中调用wait的所有的线程。
如何解决生产者和消费者的问题?
可以通过设置一个标记,表示数据的(存储空间的状态)例如,当消费者读取了(消费了一次)一次数据之后可以将标记改为false,当生产者生产了一个数据,将标记改为true。
,也就是只有标记为true的时候,消费者才能取走数据,标记为false时候生产者才生产数据。
代码实现:
package cn.itcast.gz.runnable; public class Demo10 {
public static void main(String[] args) {
Person p = new Person();
Producer pro = new Producer(p);
Consumer con = new Consumer(p);
Thread t1 = new Thread(pro, "生产者");
Thread t2 = new Thread(con, "消费者");
t1.start();
t2.start();
}
} // 使用Person作为数据存储空间
class Person {
String name;
String gender;
boolean flag = false; public synchronized void set(String name, String gender) {
if (flag) {
try {
wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
this.name = name;
this.gender = gender;
flag = true;
notify();
} public synchronized void read() {
if (!flag) {
try {
wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
System.out.println("name:" + this.name + "----gender:" + this.gender);
flag = false;
notify();
} } // 生产者
class Producer implements Runnable {
Person p; public Producer() { } public Producer(Person p) {
this.p = p;
} @Override
public void run() {
int i = 0;
while (true) { if (i % 2 == 0) {
p.set("jack", "man");
} else {
p.set("小丽", "女");
}
i++; } } } // 消费者
class Consumer implements Runnable {
Person p; public Consumer() { } public Consumer(Person p) {
this.p = p;
} @Override
public void run() { while (true) {
p.read(); }
} }
线程间通信其实就是多个线程在操作同一个资源,但操作动作不同,wait,notify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些方法定义在Object类中
因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被统一锁上notify唤醒,不可以对不同锁中的线程进行唤醒,就是等待和唤醒必须是同一个锁。而锁由于可以使任意对象,所以可以被任意对象调用的方法定义在Object类中
wait() 和 sleep()有什么区别?
wait():释放资源,释放锁。是Object的方法
sleep():释放资源,不释放锁。是Thread的方法
定义了notify为什么还要定义notifyAll,因为只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。
线程的停止
Stop()方法已经不推荐使用了
Interrupt()方法不用在同步代码快中使用 该方法强制清除线程的等待状态 该方法简单粗暴,强制清除等待状态,会抛出异常。Noitify()方法比较温和,能够唤醒阻塞队列里的某个线程但无法指定具体的某个线程。
线程的停止方法:
1、一般通过一个变量去控制,线程的主要内容是run()方法,这个方法中我们一般写的是一个while(true){}的循环,所以我们一般定义一个flag 用一个标志变量来控制线程的结束,这是比较好的一种用法。
2、如果要停止一个处于“等待”状态下的线程,我们需要变量配合或notify方法或者interrupt方法。
线程生命周期
任何事物都是生命周期,线程也是,
1. 正常终止 当线程的run()执行完毕,线程死亡。
2. 使用标记停止线程
注意:Stop方法已过时,就不能再使用这个方法。
如何使用标记停止线程停止线程。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,线程就结束。
class StopThread implements Runnable {
public boolean tag = true;
@Override
public void run() {
int i = 0; while (tag) {
i++;
System.out.println(Thread.currentThread().getName() + "i:" + i);
}
}
}
public class Demo8 {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread th = new Thread(st, "线程1");
th.start();
for (int i = 0; i < 100; i++) {
if (i == 50) {
System.out.println("main i:" + i);
st.tag = false;
}
}
}
}
后台线程
后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。
实现:
setDaemon(boolean on)
特点:
当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。
必须在启动线程之前(调用start方法之前)调用setDaemon(true)方法,才可以把该线程设置为后台线程。
一旦main()执行完毕,那么程序就会终止,JVM也就退出了。
可以使用isDaemon() 测试该线程是否为后台线程(守护线程)。
该案例:开启了一个qq检测升级的后台线程,通过while真循环进行不停检测,当计数器变为100的时候,表示检测完毕,提示是否更新,线程同时结束。
为了验证,当非后台线程结束时,后台线程是否终止,故意让该后台线程睡眠一会。发现只要main线程执行完毕,后台线程也就随之消亡了。
class QQUpdate implements Runnable {
int i = 0; @Override
public void run() {
while (true) { System.out.println(Thread.currentThread().getName() + " 检测是否有可用更新");
i++;
try {
Thread.sleep(10);
} catch (InterruptedException e) { e.printStackTrace();
}
if (i == 100) {
System.out.println("有可用更新,是否升级?");
break;
}
}
}
}
public class Demo9 {
public static void main(String[] args) {
QQUpdate qq = new QQUpdate();
Thread th = new Thread(qq, "qqupdate");
th.setDaemon(true);
th.start();
System.out.println(th.isDaemon());
System.out.println("hello world");
}
}
Thread的join方法
当A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行
本案例,启动了一个JoinThread线程,main(主线程)进行for循环,当计数器为50时,让JoinThread,通过join方法,加入到主线程中,发现只有JoinThread线程执行完,主线程才会执行完毕.
可以刻意让JoinThread线程sleep,如果JoinThread没有调用join方法,那么肯定是主线程执行完毕,但是由于JoinThread线程加入到了main线程,必须等JoinThread执行完毕主线程才能继续执行。
class JoinThread implements Runnable { @Override
public void run() {
int i = 0;
while (i < 300) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " i:" + i);
i++;
}
}
} public class Demo10 {
public static void main(String[] args) throws InterruptedException {
JoinThread jt = new JoinThread();
Thread th = new Thread(jt, "one");
th.start();
int i = 0;
while (i < 200) {
if (i == 100) {
th.join();
}
System.err.println(Thread.currentThread().getName() + " i:" + i);
i++; }
}
}
(6)简单说说java中的线程的更多相关文章
- 并发王者课 - 青铜 2:峡谷笔记 - 简单认识Java中的线程
在前面的<兵分三路:如何创建多线程>文章中,我们已经通过Thread和Runnable直观地了解如何在Java中创建一个线程,相信你已经有了一定的体感.在本篇文章中,我们将基于前面的示例代 ...
- Java中的线程
http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ...
- JAVA中创建线程的三种方法及比较
JAVA中创建线程的方式有三种,各有优缺点,具体如下: 一.继承Thread类来创建线程 1.创建一个任务类,继承Thread线程类,因为Thread类已经实现了Runnable接口,然后重写run( ...
- 简单聊聊java中的final关键字
简单聊聊java中的final关键字 日常代码中,final关键字也算常用的.其主要应用在三个方面: 1)修饰类(暂时见过,但是还没用过); 2)修饰方法(见过,没写过); 3)修饰数据. 那么,我们 ...
- 浅谈利用同步机制解决Java中的线程安全问题
我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等 ...
- 第9章 Java中的线程池 第10章 Exector框架
与新建线程池相比线程池的优点 线程池的分类 ThreadPoolExector参数.执行过程.存储方式 阻塞队列 拒绝策略 10.1 Exector框架简介 10.1.1 Executor框架的两级调 ...
- Java中的线程状态转换和线程控制常用方法
Java 中的线程状态转换: [注]:不是 start 之后就立刻开始执行, 只是就绪了(CPU 可能正在运行其他的线程). [注]:只有被 CPU 调度之后,线程才开始执行, 当 CPU 分配给你的 ...
- java中的线程安全
在Java中,线程的安全实际上指的是内存的安全,这是由操作系统决定的. 目前主流的操作系统都是多任务的,即多个进程同时运行.为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的.分配给别 ...
- Java多线程编程(1)--Java中的线程
一.程序.进程和线程 程序是一组指令的有序集合,也可以将其通俗地理解为若干行代码.它本身没有任何运行的含义,它只是一个静态的实体,它可能只是一个单纯的文本文件,也有可能是经过编译之后生成的可执行文 ...
随机推荐
- Professional C# 6 and .NET Core 1.0 - What’s New in C# 6
本文为转载,学习研究 What's New in C# 6 With C# 6 a new C# compiler is available. It's not only that a source ...
- 蓝桥网试题 java 基础练习 十进制转十六进制
---------------------------------------------------------------------------------------------------- ...
- asp.net权限认证:OWIN实现OAuth 2.0 之授权码模式(Authorization Code)
asp.net权限认证系列 asp.net权限认证:Forms认证 asp.net权限认证:HTTP基本认证(http basic) asp.net权限认证:Windows认证 asp.net权限认证 ...
- [JS][jQuery]清空元素html("")、innerHTML="" 与 empty()的区别 、remove()区别
清空元素html("").innerHTML="" 与 empty()的区别 一.清空元素的区别 1.错误做法一: $(" ...
- [商业_法务] 2、注册公司起名很费劲,用C++怒写个随机名字生成器
前言 博主最近在注册公司,由于之前听说过注册公司的名字很难通过,于是便直接找代理去帮忙跑趟,为确保万无一失,还自己绞尽脑汁想了几个很奇葩的名字(噬菌体.云木.灌木.杏仁...). 但是不幸的是那些奇葩 ...
- 【easyui】之treegrid的分页
easyui官网给的treegrid的分页是相当的复杂,我们来简化一下! 首先treegrid 分页和 datagrid一样需要设置一系列参数! 如下: depTreeGrid=$("#de ...
- Python 接口测试(一)
@font-face { font-family: "Times"; }@font-face { font-family: "宋体"; }@font-face ...
- 如何在 Windows上编译Objective-C
Objective-C现在几乎已经变成了苹果的专利了,可以直接在苹果的Xcode上编译Objective-C程序,但是在Windows平台下的编译工具就寥寥无几了,本身这种语言用的人就不是很多.今天在 ...
- protocol error, got 'n' as reply type byte
centos6.5上安装redis3.2版本,本地访问redis报错protocol error, got 'n' as reply type byte 解决办法 在redis配置文件redis.co ...
- ORACLE-EXP和IMP方法介绍
说明: 之前经常有人询问数据库导出导入的方法,去百度查询,但是查询的结果却不是很尽如人意,所以在此做个基本的总结,包括 导出:(导出用户,导出表,导出表结构不带数据,QUERY参数),导入(导入数据文 ...