前言
JAVA线程是JAVA中重要的一个知识点,线程又可分为单线程和多线程,然而首先什么是线程?接下来我们慢慢深入了解
目录
- 线程的介绍
- 线程的使用
- 线程的优先级别
- 守护线程
- 线程的同步
一、线程的介绍
世间万物都可以同时完成很多工作,例如:人体可以同时进行呼吸,吃东西,思考问题等活动;
用户既可以使用计算机听歌,也可以使用它打印文件,而这些活动完全可以同时进行;
这种思想放在JAVA中被称为并发,而将并发完成的每一件事情称为线程。
一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。
在单线程中,程序代码按调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要产生多线程。
简单理解一下,单线程就好像排队上厕所,有序进行,而多线程就是争抢着上厕所(大家都很急嘛O(∩_∩)O哈哈),无序进行。
具体代码我们看下面
/*
* 单线程
* 一个线程:main,test01,test02
* main是主线程
*/
public class ThreadTest01 {
public static void main(String[] args) {
test01();
}
public static void test01(){
test02();
}
public static void test02(){
System.out.println("其实我是test03");
}
}
这是一个简单的单线程,顺序往下调用,下面我们看看多线程的实现
二、线程的使用
实现多线程有以下俩种方法
继承Thread类:
Thread类是 java.lang 包中的一个类,从这个类中实例化的对象代表线程,程序员启动一个新线程需要建立 Thread 实例;
继承 Thread 类创建一个新的线程语法如下:
Public class ThreadTest extends Thread{
}
当一个类继承Thread类后,就可以在该类中覆盖 run() 方法,将实现该线程功能的代码写入 run() 方法中
Public void run(){
}
然后同时调用Thread类中的Start()方法执行线程,也就是调用run()方法
Public static void main(String args[]){
new ThreadTest().start();
}
使用 Runnable 接口:
实现 Runnable 接口的语法如下:
Public class Thread extends Object implements Runnable
使用 Runnable 接口启动新的线程步骤如下:
(1)建立 Runnable 对象;
(2)使用参数为 Runnable 对象的构造方法创建Thread实例;
(3)调用start()方法启动线程。
import java.lang.*;
/*
* 多线程 有俩种实现方式
* 1:继承Thread类
* 2:实现Runnable
*/
public class ThreadTest02 {
public static void main(String[] args) {
/*第一种:继承Thread*/
/*
Thread hh = new C1();
hh.start();
*/
/*第二种:实现Runnable*/
C2 tt = new C2();//实例化一个对象 但是没有直接的start()方法 因为它不是一个线程
Thread hh = new Thread(tt);//Thread要求将runnable对象传入构成线程
hh.start();
for (int i = 0; i < 30; i++) {
System.out.println("main->"+i);
}
}
}
/*
class C1 extends Thread{
public void run(){
for (int i = 0; i < 30; i++) {
System.out.println("run->"+i);
}
}
}
*/
class C2 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 30; i++) {
System.out.println("run->"+i);
}
}
}
运行结果如图,main 线程 与 run 线程是同时进行的!!!另外我们也可以给线程取名字和获取名字,只需要三步☟
- currentThread() 获取当前线程的对象
- getName() 获得线程的名字
- setName() 给线程取名字
三、线程的优先级别:
多线程的执行本身就是多个线程的交换执行,并非同时执行每个线程执行时都具有一定的优先级;
当调度线程时,会优先考虑级别高的线程
默认情况下,一个线程继承其父线程的优先级
使用 线程对象.setPriority(p)来改变线程的优先级
优先级影响CPU在线程间切换,切换的原则是:
当一个线程通过显式放弃、睡眠或者阻塞、自愿释放控制权时,所有线程均接受检查而优先级高线程将会优先执行
一个线程可以被一个高优先级的线程抢占资源
同级别的线程之间,则通过控制权的释放,确保所有的线程均有机会运行。
但是这里要注意的一点,优先级是一个高概率事件,并非高优先级就一定比低优先级的线程执行,就跟之前打的比方争厕所,高优先级的线程是跑的快的人,但低优先级的壮士也不是吃素的哈哈。。。。好了,回到正题看代码
/*
* 线程有优先级 1-10 默认优先级为5
*/
public class ThreadTest04 {
public static void main(String[] args) {
Thread h1 = new Test();
Thread h2 = new Test();
System.out.println("最大优先级:"+Thread.MAX_PRIORITY);
System.out.println("最小优先级:"+Thread.MIN_PRIORITY);
System.out.println("默认优先级:"+Thread.NORM_PRIORITY);
h1.setName("线程1");
//查看线程优先级
h1.setPriority(1);
System.out.println("线程1的优先级:"+h1.getPriority());
h2.setName("线程2");
h2.setPriority(10);
System.out.println("线程2的优先级:"+h2.getPriority());
h1.start();
h2.start();
//优先级高的只是提高优先概率 并不是绝对的优先
}
}
class Test extends Thread{
public void run(){
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName()+"->"+i);
}
}
}
至于运行结果,大家就自己自行查看这里就不贴图了!!
四、守护线程
在Java中有两类线程:User Thread (用户线程)、Daemon Thread (守护线程) Daemon 的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。
User 和 Daemon 两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下 Daemon Thread 存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon 也就没有工作可做了,也就没有继续运行程序的必要了。
语法:
public final void setDaemon(boolean on)
注意:thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会跑出一个 IllegalThreadStateException 异常。你不能把正在运行的常规线程设置为守护线程。
package com.tz.util;
/*
* 守护线程:例如gc垃圾回收机制
* 当用户线程全部退出时 守护线程也会在自动退出
* 不能把正在运行的常规线程 设置为守护线程
* 在守护线程中产生的新线程也是守护线程
* 不是所有的应用都可以分配守护线程,比如读写的操作,计算的逻辑
*/
public class DaemonThread {
public static void main(String[] args) {
Thread c1 = new MyCommon();
Thread c2 = new Thread(new MyDaemon());
//设置守护线程
c2.setDaemon(true);
c1.start();
c2.start();
}
}
class MyCommon extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i++) {
System.out.println("线程第"+i+"次执行");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class MyDaemon implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 999999L; i++) {
System.out.println("后台线程第"+i+"次执行");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
通过代码,我们设置守护线程执行999999次,而唯一的用户线程只执行5次,从运行结果看出,当用户线程执行完毕后守护线程也随它而去,双双结束了自己的生命
五、线程的同步
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法,也称为加锁。
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
打个比方,车站买车票有很多个售票窗口,当有俩个窗口对同一张票操作时如果没有同步数据,则会出现一张票卖给了俩个人的情况,这样在车上可就尴尬了(\O_
package com.tz.util;
/*
* 线程的同步
* 同步:比如火车站买票 俩个客户在同一个窗口不能买到同一张票
*/
public class SyncharonizedThreadTest {
static class Bank{
public int account = 100;
public int getAcount(){
return account;
}
/*
* 用同步方法实现同步 整个方法所有的代码都被同步
* 同步方法是对this对象加锁
*/
public synchronized int save(int money){
account += money;
return account;
}
/*
* 也可以用同步代码块
* 优点在于可以缩小同步范围和随意加锁
*/
public int save1(int money){
synchronized (this) {
account += money;
}
return account;
}
//去掉synchronized同步结果数据不一定正确
public int save2(int money){
account += money;
return account;
}
}
class MyThread implements Runnable{
public Bank bank;
public MyThread(Bank bank){
this.bank = bank;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i++) {
System.out.println("用户余额为"+bank.save2(10));
}
}
}
/*
* 开启了俩个线程,分别对账户进行存钱
* 由于开启 了线程的同步,最后的结果肯定是正确的
*/
public void useThread(){
Bank bank = new Bank();
MyThread thread = new MyThread(bank);
System.out.println("线程1");
Thread t1 = new Thread(thread);
t1.start();
System.out.println("线程2");
Thread t2 = new Thread(thread);
t2.start();
}
public static void main(String[] args) {
SyncharonizedThreadTest st = new SyncharonizedThreadTest();
st.useThread();
}
}
save是使用关键字 synchronized 加锁方法,save1 是使用代码块,好处是可以控制同步范围(例如给有需要的数据加锁)和随意加锁,save2则是不加锁的情况,所以运行结果可能会出现差错,如图
正确答案应该是存钱200,而答案是190就是没有加锁的原因!!
关于线程的讲解大概就这些了,转载请注明出处☞ http://blog.csdn.net/W_ILU/article/details/50970031