@w460461339
2016-11-16T07:09:02.000000Z
字数 7218
阅读 825
Java基础
妈蛋= - 今天因为安装机械手出错被批评了…自己怎么那么不细心。下次安装的时候千万不要图省事漏安装零件,老老实实一个一个的安装!
今天主要学习了一下多线程的定义,构造以及使用,下面直接开始吧。
首先,我们需要知道什么是进程。
打开任任务管理器后,我们发现里面有一个进程选项,可以查看当前开启的进程。因此,我们知道,进程就是目前正在运行的程序。
多进程概述:
正在运行的程序,是系统进行资源分配和清理使用的独立单位
每一个进程都有它自己的内存空间和系统资源
而对于一个类似于迅雷这样的程序,可以同时对多个目标进行下载,那么每一个下载都是一个单独的线程,因此每一个进程里面可以包含多线程。
多线程概述:
是进程中的单个顺序控制流,是一条执行语句;
一个进程如果只有一条执行路径,就是单线程的;
一个进程如果有多条志新路径,就是多线程的。
多进程的目的是提高CPU的使用率;
多线程的目的是让该进程更加重要,提高该进程使用CPU的使用率。
另外,多线程具有随机性, 不敢保证在哪个时刻是哪个线程抢到了CPU资源。
这就是,多线程。
![此处输入图片的描述][1]
让自定义类进程Thread类,并重写run方法,在run方法中放置需要在线程中跑的程序。在测试类中创建该自定义类的对象,并调用start方法即可(该方法在Thread中)
构造类
package myThread;
//继承Thread类
public class MyThreadDemo extends Thread {
//重写run方法,将要在线程中运行的程序写入;
//一般都是写入比较费时的程序
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<200;i++){
//getName方法,得到当前类的名字
System.out.println(this.getName()+":"+i);
}
}
}
测试类
package myThread;
public class myThread_test {
public static void main(String[] args) {
//创建自定义类对象;(也是创建线程对象)
//需要几个线程就创建几个对象
MyThreadDemo mtd1=new MyThreadDemo();
MyThreadDemo mtd2=new MyThreadDemo();
//重置当前类的名字
mtd1.setName("6666");
mtd2.setName("7777");
//开始线程,注意不能用run
mtd1.start();
mtd2.start();
//获取主线程的名字
//这里除了main主线程外,还有一个回收机制,
//所以算上自己写的两个,应该有4个线程。
System.out.println(Thread.currentThread().getName());
}
}
多线程调用CPU资源采用抢占式调用模型,优先级越高越有可能抢到,优先级相同就随机分配。(注意,不是优先级高就一定能够抢到的!)
另外,默认优先级是5,最低是1,最高是10,只能在这个范围内选取。
构造类同上
package myThread;
public class myThread_test {
public static void main(String[] args) {
MyThreadDemo mtd1=new MyThreadDemo();
MyThreadDemo mtd2=new MyThreadDemo();
mtd1.setName("6666");
mtd2.setName("7777");
//输出默认优先级,为5
System.out.println(mtd1.getPriority());
//重新设置其优先级为10
mtd1.setPriority(10);
//结果可以发现,mtd1和mtd2仍然会交叉出现,
//但mtd1明显在前面出现的多。
mtd1.start();
mtd2.start();
System.out.println(Thread.currentThread().getName());
}
}
sleep方法,让当前线程暂停一定时间。因为针对当前线程,所以是写在构造类中。
构造类
package myThread;
import java.util.Date;
public class MyThreadDemo extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<200;i++){
System.out.println(this.getName()+":"+i);
System.out.println(new Date());
//由于覆盖了方法, 所以异常只能try catch
try {
//休眠1000ms,当前线程进入阻塞状态。
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
等待该线程终止。
构造类就是最简单的,没有sleep函数的那种
public class MyThreadJoin {
//在调用join方法是抛出异常
public static void main(String[] args) throws InterruptedException {
//创建自定义对象
MyThreadDemo myd1=new MyThreadDemo();
MyThreadDemo myd2=new MyThreadDemo();
MyThreadDemo myd3=new MyThreadDemo();
//设置名字
myd1.setName("Thread1");
myd2.setName("Thread2");
myd3.setName("Thread3");
//开始Thread1
myd1.start();
//设置仅当Thread1走完了,才能走Thread2,以及3
myd1.join();
myd2.start();
//设置仅当Thread2走完了才能走Thread3
myd2.join();
myd3.start();
}
}
暂停当前正在执行的线程对象,并执行其他线程。
//构造类
public class MyThreadDemo extends Thread {
@Override
public void run() {
for(int i=0;i<200;i++){
System.out.println(this.getName()+":"+i);
Thread.yield();
}
}
}
package myThread;
public class MyThreadJoin {
public static void main(String[] args) throws InterruptedException {
MyThreadDemo myd1=new MyThreadDemo();
MyThreadDemo myd2=new MyThreadDemo();
myd1.setName("Thread1");
myd2.setName("Thread2");
myd1.start();
myd2.start();
}
}
很大概率出现你一次我一次的情况
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
意思是,在某个线程A里又调用的其他的线程B和C,设置B和C为A的后台线程,则当A结束时,B和C一定会马上结束。
package myThread;
public class MysetDaemon {
public static void main(String[] args) {
MyThreadDemo myd1=new MyThreadDemo();
MyThreadDemo myd2=new MyThreadDemo();
myd1.setName("张飞");
myd2.setName("关羽");
//此时这些程序都在主线程里开启
//myd1和myd2设置为主线程的后台(守护)线程
//当主线程完了时,不管myd1和myd2是否走完了,都不会继续下一个
//但会向前抽抽两下…
myd1.setDaemon(true);
myd2.setDaemon(true);
Thread.currentThread().setName("刘备");
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
myd1.start();
myd2.start();
}
}
public class MyThreadDemo extends Thread {
@Override
public void run() {
//次数要够多,不然太少,会在抽抽的时候抽光,就看不出效果。
for(int i=0;i<1000;i++){
System.out.println(this.getName()+":"+i);
}
}
}
比如在构造类中,要让当前线程睡10秒再走,但是在测试类中说,只能睡3秒,3秒之后就起。这个时候就需要用到interrupt
构造类
public class MyThreadDemo extends Thread {
@Override
public void run() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
System.out.println("线程结束");
}
}
测试类
public class MyThreadInterrupt {
public static void main(String[] args) {
MyThreadDemo myd1=new MyThreadDemo();
myd1.start();
//开始只让睡3s,之后中断
//此时构造类中有sleep仍在执行,因此interrupt向构造类中sleep抛出一个InterruptedException的异常,中断构造类中sleep
//
try {
Thread.sleep(3000);
myd1.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
因此显示结果为:线程被中断,线程结束。
构造类
public class MyThreadDemo implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<1000;i++){
//注意,由于实现了Runnable接口,所以不能直接利用this.getName来获取当前线程的名字
//只能通过利用Tread.currentThread().getName()来得到
System.out.println(Thread.currentThread().getName()+
":"+i);
}
}
}
测试类
public class MyTreadWay2 {
public static void main(String[] args) {
//只需要创建一个测试类对象
MyThreadDemo myd=new MyThreadDemo();
//就可以构造出许多的线程
Thread td1=new Thread(myd,"Thread1");
Thread td2=new Thread(myd,"Thread2");
Thread td3=new Thread(myd,"Thread3");
td1.start();
td2.start();
td3.start();
}
}
第二种创建多线程的方法比较好的实现了数据(一般放在run外面)和程序(放在run里面)的分离
需求:
三个售票窗口,总共一百张票,出完为止。
网络会出现一些延迟,需要添加延迟消除网络延迟影响。
要求:
不能重复卖票;
不能多卖票;
利用第一种方法和第二种方法实现多线程,来创建这个程序,无非就是成员变量是不是静态的问题。因为第二种方法能够更好的表现出数据和程序的分离,所以这里使用第二种方法。
构造类
package sellTicket;
public class Ticket implements Runnable{
private int ticket=100;
@Override
public void run() {
//这个while是为了保证有票就一直卖
while(true){
if(ticket>0){
//延迟,消除网络延迟影响
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"已经出售第"+(ticket--)+"张票");
}
}
}
}
测试类
package sellTicket;
public class Sell {
public static void main(String[] args) {
Ticket tc=new Ticket();
Thread td1=new Thread(tc,"售票窗口1");
Thread td2=new Thread(tc,"售票窗口2");
Thread td3=new Thread(tc,"售票窗口3");
td1.start();
td2.start();
td3.start();
}
}
此时会出现重复卖票以及卖到第0张和第-1张票的情况。
A:重复卖票
原因:CPU的一次操作必须是原子性的。
B:出现了负数
原因:随机性和延迟导致的。
解决方案:
在A线程执行时,锁死该部分程序,直到A线程该部分程序结束,才能继续多个线程继续争抢。
构造类,测试类是一样的
package sellTicket;
public class Ticket implements Runnable{
private int ticket=100;
private Object obj=new Object();
@Override
public void run() {
while(true){
//利用synchronized保住所有需要锁住的代码
//obj可以是任意类的任意对象,但必须定义在run外
synchronized(obj){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"已经出售第"+(ticket--)+"张票");
}
}
}
}
}
synchronized的格式:
synchronized(类的对象){
要锁住的代买
}
方法也可以被锁住:
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
synchronized这个其实叫做同步代码块,锁住需要被锁住的代码内容。主要用来保证多线程的安全。具体的的深入了解还没有= -
线程是否有问题的判断标准:
A:是否是多线程环境
B:是否有共享数据(比如例子中成员变量ticket就是)
C:是否多条操作语句操作共享数据
如何解决问题:
基本思想:把多个语句操作共享数据的代码块锁起来,让任意时刻只能有一个线程执行这些代码块
同步的前提:
A:多线程
B:多个线程使用的是同一个锁对象(定义在run外的obj)
同步的好处以及弊端:
好处:解决了多线程安全问题;
弊端:线程太多时,会消耗大量资源
锁对象问题:
同步代码块:锁对象为任意类的任意对象
同步成员方法:锁对象为this,及本类的当前对象
同步静态方法/变量:锁对象为类的字节码对象(反射内容)
之前讲过,vector是线程安全的,ArrayList是线程不安全的。但是当我们需要线程安全时,也不用vector,是因为我们有更高效的方式。
synchronizedCollection(Collection< T > c);
返回指定 collection 支持的同步(线程安全的)collection。
synchronizedList(List< T > list) 返回指定列表支持的同步(线程安全的)列表。
……还有几个就不列了,那么可以通过这个来创建一个线程安全的ArrayList
List<String> list=Collections.synchronizedList(
new ArrayList<String>());
(终于全部写完了…可以安心的玩一会了~)